/* proto.c
 * Routines for protocol tree
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <float.h>
#include <inttypes.h>
#include <errno.h>

#include "register-int.h"

#include "wmem_strbuf.h"

#include "ftypes.h"
#include "sign_ext.h"
#include "tvbuff.h"
#include "packet.h"
#include "strutil.h"
#include "unit_strings.h"
#include "bits_ctz.h"
#include "bits_count_ones.h"

#include "proto.h"

#include "utf8_entities.h"

/* Ptvcursor limits */
#define SUBTREE_ONCE_ALLOCATION_NUMBER 8
#define SUBTREE_MAX_LEVELS 256

#define max_tree_items 32

typedef struct __subtree_lvl {
    int         cursor_offset;
    proto_item *it;
    proto_tree *tree;
} subtree_lvl;

struct ptvcursor {
//    wmem_allocator_t *scope;
    subtree_lvl *pushed_tree;
    uint8_t         pushed_tree_index;
    uint8_t         pushed_tree_max;
    proto_tree  *tree;
    tvbuff_t    *tvb;
    int         offset;
};

#define cVALS(x) (const value_string*)(x)

/** See inlined comments.
 @param tree the tree to append this item to
 @param free_block a code block to call to free resources if this returns
 @return NULL if 'tree' is null */
#define CHECK_FOR_NULL_TREE_AND_FREE(tree, free_block)            \
    if (!tree) {                            \
        free_block;                        \
        return NULL;                        \
    }

/** See inlined comments.
 @param tree the tree to append this item to
 @param free_block a code block to call to free resources if this returns
 @return NULL if 'tree' is null */
#define CHECK_FOR_NULL_TREE(tree) \
    CHECK_FOR_NULL_TREE_AND_FREE(tree, ((void)0))

/** See inlined comments.
 @param length the length of this item
 @param cleanup_block a code block to call to free resources if this returns
 @return NULL if 'length' is lower -1 or equal 0 */
#define CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length, cleanup_block)    \
    if (length < -1 || length == 0 ) {                \
        cleanup_block;                        \
        return NULL;                        \
    }

/** See inlined comments.
 @param length the length of this item
 @return NULL if 'length' is lower -1 or equal 0 */
#define CHECK_FOR_ZERO_OR_MINUS_LENGTH(length) \
    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length, ((void)0))

/** See inlined comments.
 @param tree the tree to append this item to
 @param hfindex field index
 @param hfinfo header_field
 @param free_block a code block to call to free resources if this returns
 @return the header field matching 'hfinfo' */
#define TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfindex, hfinfo, free_block) \
    /* If the tree is not visible and this item is not referenced    \
       we don't have to do much work at all but we should still    \
       return a node so that referenced field items below this node    \
       (think proto_item_add_subtree()) will still have somewhere    \
       to attach to or else filtering will not work (they would be    \
       ignored since tree would be NULL).                \
       DON'T try to fake a node where PTREE_FINFO(tree) is NULL    \
       since dissectors that want to do proto_item_set_len() or    \
       other operations that dereference this would crash.        \
       DON'T try to fake a node where PTREE_FINFO(tree) is visible    \
       because that means we can change its length or repr, and we    \
       don't want to do so with calls intended for this faked new    \
       item, so this item needs a new (hidden) child node.        \
       (PROTO_ITEM_IS_HIDDEN(tree) checks both conditions.)        \
       We fake FT_PROTOCOL unless some clients have requested us    \
       not to do so.                        \
    */                                \
    PTREE_DATA(tree)->count++;                    \
    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);            \
    if (PTREE_DATA(tree)->count > max_tree_items) {    \
        free_block;                        \
        /* Let the exception handler add items to the tree */    \
        PTREE_DATA(tree)->count = 0;                \
    }                                \
    if (!(PTREE_DATA(tree)->visible)) {                \
        if (PROTO_ITEM_IS_HIDDEN(tree)) {            \
            if ((hfinfo->ref_type != HF_REF_TYPE_DIRECT)    \
                && (hfinfo->ref_type != HF_REF_TYPE_PRINT)    \
                && (hfinfo->type != FT_PROTOCOL ||        \
                PTREE_DATA(tree)->fake_protocols)) {    \
                free_block;                \
                /* just return tree back to the caller */\
                return tree;                \
            }                        \
        }                            \
    }

/** See inlined comments.
 @param tree the tree to append this item to
 @param hfindex field index
 @param hfinfo header_field
 @return the header field matching 'hfinfo' */
#define TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo) \
    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfindex, hfinfo, ((void)0))


/** See inlined comments.
 @param pi the created protocol item we're about to return */
#define TRY_TO_FAKE_THIS_REPR(pi)    \
    if (!(PTREE_DATA(pi)->visible) && \
          PROTO_ITEM_IS_HIDDEN(pi)) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return pi; \
    }
/* Same as above but returning void */
#define TRY_TO_FAKE_THIS_REPR_VOID(pi)    \
    if (!pi)            \
        return;            \
    if (!(PTREE_DATA(pi)->visible) && \
          PROTO_ITEM_IS_HIDDEN(pi)) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return; \
    }
/* Similar to above, but allows a NULL tree */
#define TRY_TO_FAKE_THIS_REPR_NESTED(pi)    \
    if ((pi == NULL) || (!(PTREE_DATA(pi)->visible) && \
        PROTO_ITEM_IS_HIDDEN(pi))) { \
        /* If the tree (GUI) or item isn't visible it's pointless for \
         * us to generate the protocol item's string representation */ \
        return pi; \
    }

#ifdef ENABLE_CHECK_FILTER
#define CHECK_HF_VALUE(type, spec, start_values) \
{ \
    const type *current; \
    int n, m; \
    current = start_values; \
    for (n=0; current; n++, current++) { \
        /* Drop out if we reached the end. */ \
        if ((current->value == 0) && (current->strptr == NULL)) { \
            break; \
        } \
        /* Check value against all previous */ \
        for (m=0; m < n; m++) { \
            /* There are lots of duplicates with the same string, \
               so only report if different... */ \
            if ((start_values[m].value == current->value) && \
                (strcmp(start_values[m].strptr, current->strptr) != 0)) { \
                printf("Field '%s' (%s) has a conflicting entry in its" \
                      " value_string: %" spec " is at indices %u (%s) and %u (%s)", \
                      hfinfo->name, hfinfo->abbrev, \
                      current->value, m, start_values[m].strptr, n, current->strptr); \
            } \
        } \
    } \
}
#endif

/* The longest NUMBER-like field label we have is for BASE_OUI, which
 * can have up to 64 bytes for the manufacturer name if resolved plus
 * 11 bytes for the "XX:XX:XX ()" part = 75 octets.
 */
#define NUMBER_LABEL_LENGTH 80

static const char *hf_try_val_to_str(uint32_t value, const header_field_info *hfinfo);
static const char *hf_try_val64_to_str(uint64_t value, const header_field_info *hfinfo);
static const char *hf_try_val_to_str_const(uint32_t value, const header_field_info *hfinfo, const char *unknown_str);
static const char *hf_try_val64_to_str_const(uint64_t value, const header_field_info *hfinfo, const char *unknown_str);
static int hfinfo_bitoffset(const header_field_info *hfinfo);
static int hfinfo_mask_bitwidth(const header_field_info *hfinfo);
static int hfinfo_container_bitwidth(const header_field_info *hfinfo);

#define label_concat(dst, pos, src) \
    label_strcpy(dst, ITEM_LABEL_LENGTH, pos, src, 0)

static void mark_truncated(char *label_str, size_t name_pos, const size_t size);
static void label_mark_truncated(char *label_str, size_t name_pos);
#define LABEL_MARK_TRUNCATED_START(label_str) label_mark_truncated(label_str, 0)

static void fill_label_boolean(const field_info *fi, char *label_str);
static void fill_label_bitfield_char(const field_info *fi, char *label_str);
static void fill_label_bitfield(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_bitfield64(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_char(const field_info *fi, char *label_str);
static void fill_label_number(const field_info *fi, char *label_str, bool is_signed);
static void fill_label_number64(const field_info *fi, char *label_str, bool is_signed);

static size_t fill_display_label_float(const field_info *fi, char *label_str);
static void fill_label_float(const field_info *fi, char *label_str);

static const char *hfinfo_number_value_format_display(const header_field_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_value_format_display64(const header_field_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_char_vals_format(const header_field_info *hfinfo, char buf[32], uint32_t value);
static const char *hfinfo_number_vals_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_vals_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_number_value_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_number_value_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);
static const char *hfinfo_char_value_format(const header_field_info *hfinfo, char buf[32], uint32_t value);
static const char *hfinfo_numeric_value_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value);
static const char *hfinfo_numeric_value_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value);

static void proto_cleanup_base(void);

static proto_item *proto_tree_add_node(proto_tree *tree, field_info *fi);

static void get_hfi_length(header_field_info *hfinfo, tvbuff_t *tvb, const int start, int *length,
        int *item_length, const unsigned encoding);

static int get_full_length(header_field_info *hfinfo, tvbuff_t *tvb, const int start,
        int length, unsigned item_length, const int encoding);

static field_info *new_field_info(proto_tree *tree, header_field_info *hfinfo, tvbuff_t *tvb,
           const int start, const int item_length);

static proto_item *proto_tree_add_pi(proto_tree *tree, header_field_info *hfinfo, tvbuff_t *tvb,
          int start, int *length);

static void proto_tree_set_representation_value(proto_item *pi, const char *format, va_list ap);
static void proto_tree_set_representation(proto_item *pi, const char *format, va_list ap);

static void proto_tree_set_protocol_tvb(field_info *fi, tvbuff_t *tvb, const char* field_data, int length);

static void proto_tree_set_bytes(field_info *fi, const uint8_t* start_ptr, int length);
static void proto_tree_set_bytes_tvb(field_info *fi, tvbuff_t *tvb, int offset, int length);
static void proto_tree_set_bytes_gbytearray(field_info *fi, const GByteArray *value);

static void proto_tree_set_string(field_info *fi, const char* value);

//static void
//proto_tree_set_time(field_info *fi, const nstime_t *value_ptr);
//static void
//proto_tree_set_ax25(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_ax25_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_vines(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_vines_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_ether(field_info *fi, const uint8_t* value);
//static void
//proto_tree_set_ether_tvb(field_info *fi, tvbuff_t *tvb, int start);
//static void
//proto_tree_set_ipxnet(field_info *fi, uint32_t value);
//static void
//proto_tree_set_ipv4(field_info *fi, ws_in4_addr value);
//static void
//proto_tree_set_ipv6(field_info *fi, const ws_in6_addr* value);
//static void
//proto_tree_set_ipv6_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_fcwwn_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_guid(field_info *fi, const e_guid_t *value_ptr);
//static void
//proto_tree_set_guid_tvb(field_info *fi, tvbuff_t *tvb, int start, const unsigned encoding);
//static void
//proto_tree_set_oid(field_info *fi, const uint8_t* value_ptr, int length);
//static void
//proto_tree_set_oid_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);
//static void
//proto_tree_set_system_id(field_info *fi, const uint8_t* value_ptr, int length);
//static void
//proto_tree_set_system_id_tvb(field_info *fi, tvbuff_t *tvb, int start, int length);

static void proto_tree_set_boolean(field_info *fi, uint64_t value);
static void proto_tree_set_float(field_info *fi, float value);
static void proto_tree_set_double(field_info *fi, double value);
static void proto_tree_set_uint(field_info *fi, uint32_t value);
static void proto_tree_set_int(field_info *fi, int32_t value);
static void proto_tree_set_uint64(field_info *fi, uint64_t value);
static void proto_tree_set_int64(field_info *fi, int64_t value);

//static void
//proto_tree_set_eui64(field_info *fi, const uint64_t value);
//static void
//proto_tree_set_eui64_tvb(field_info *fi, tvbuff_t *tvb, int start, const unsigned encoding);

/* Handle type length mismatch (now filterable) expert info */
//static int proto_type_length_mismatch;
//static expert_field ei_type_length_mismatch_error;
//static expert_field ei_type_length_mismatch_warn;
//static void register_type_length_mismatch(void);

/* Handle byte array string decoding errors with expert info */
//static int proto_byte_array_string_decoding_error;
//static expert_field ei_byte_array_string_decoding_failed_error;
//static void register_byte_array_string_decodinws_error(void);

/* Handle date and time string decoding errors with expert info */
//static int proto_date_time_string_decoding_error;
//static expert_field ei_date_time_string_decoding_failed_error;
//static void register_date_time_string_decodinws_error(void);

/* Handle string errors expert info */
//static int proto_string_errors;
//static expert_field ei_string_trailing_characters;
//static void register_string_errors(void);

static int proto_register_field_init(header_field_info *hfinfo, const int parent);

/* special-case header field used within proto.c */
static header_field_info hfi_text_only =
    { "Text item",    "text", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL };
int hf_text_only;

/* Structure for information about a protocol */
struct _protocol {
    const char *name;               /* long description */
    const char *short_name;         /* short description */
    const char *filter_name;        /* name of this protocol in filters */
    GPtrArray  *fields;             /* fields for this protocol */
    int         proto_id;           /* field ID for this protocol */
    bool        is_enabled;         /* true if protocol is enabled */
    bool        enabled_by_default; /* true if protocol is enabled by default */
    bool        can_toggle;         /* true if is_enabled can be changed */
    int         parent_proto_id;    /* Used to identify "pino"s (Protocol In Name Only).
                                       For dissectors that need a protocol name so they
                                       can be added to a dissector table, but use the
                                       parent_proto_id for things like enable/disable */
};

/* List of all protocols */
static GList *protocols;

/* Structure stored for deregistered g_slice */
struct g_slice_data {
    size_t   block_size;
    void    *mem_block;
};

/* Deregistered fields */
static GPtrArray *deregistered_fields;
static GPtrArray *deregistered_data;
static GPtrArray *deregistered_slice;

/* indexed by prefix, contains initializers */
static GHashTable* prefixes;

/* Contains information about a field when a dissector calls
 * proto_tree_add_item.  */
#define FIELD_INFO_NEW(fi)  fi = wmem_new(field_info)
#define FIELD_INFO_FREE(fi) wmem_free(fi)

/* Contains the space for proto_nodes. */
#define PROTO_NODE_INIT(node)            \
    node->first_child = NULL;        \
    node->last_child = NULL;        \
    node->next = NULL;

#define PROTO_NODE_FREE(node)            \
    wmem_free(node)

/* String space for protocol and field items for the GUI */
#define ITEM_LABEL_NEW(il)            \
    il = wmem_new(item_label_t);
#define ITEM_LABEL_FREE(il)            \
    wmem_free(il);

#define PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo)                        \
    if((hfindex == 0 || (unsigned)hfindex > gpa_hfinfo.len)/* && wireshark_abort_on_dissector_bug*/)    \
        printf("Unregistered hf! index=%d", hfindex);                    \
    DISSECTOR_ASSERT_HINT(hfindex > 0 && (unsigned)hfindex < gpa_hfinfo.len, "Unregistered hf!");    \
    DISSECTOR_ASSERT_HINT(gpa_hfinfo.hfi[hfindex] != NULL, "Unregistered hf!");    \
    hfinfo = gpa_hfinfo.hfi[hfindex];

/* List which stores protocols and fields that have been registered */
typedef struct _gpa_hfinfo_t {
    uint32_t            len;
    uint32_t            allocated_len;
    header_field_info **hfi;
} gpa_hfinfo_t;

static gpa_hfinfo_t gpa_hfinfo;

/* Hash table of abbreviations and IDs */
static GHashTable *gpa_name_map;
static header_field_info *same_name_hfinfo;

/* Hash table protocol aliases. const char * -> const char * */
static GHashTable *gpa_protocol_aliases;

/*
 * We're called repeatedly with the same field name when sorting a column.
 * Cache our last gpa_name_map hit for faster lookups.
 */
static char *last_field_name;
static header_field_info *last_hfinfo;

static void save_same_name_hfinfo(void *data)
{
    same_name_hfinfo = (header_field_info*)data;
}

/* Points to the first element of an array of bits, indexed by
   a subtree item type; that array element is true if subtrees of
   an item of that type are to be expanded. */
static uint32_t *tree_is_expanded;

/* Number of elements in that array. The entry with index 0 is not used. */
int        num_tree_types = 1;

/* Name hashtables for fast detection of duplicate names */
static GHashTable* proto_names;
static GHashTable* proto_short_names;
static GHashTable* proto_filter_names;

static const char *reserved_filter_names[] = {
    /* Display filter keywords. */
    "eq",
    "ne",
    "all_eq",
    "any_eq",
    "all_ne",
    "any_ne",
    "gt",
    "ge",
    "lt",
    "le",
    "bitand",
    "bitwise_and",
    "contains",
    "matches",
    "not",
    "and",
    "or",
    "xor",
    "in",
    "any",
    "all",
    "true",
    "false",
    "nan",
    "inf",
    NULL
};

static GHashTable *proto_reserved_filter_names;

static int proto_compare_name(const void *p1_arg, const void *p2_arg)
{
    const protocol_t *p1 = (const protocol_t *)p1_arg;
    const protocol_t *p2 = (const protocol_t *)p2_arg;

    return g_ascii_strcasecmp(p1->short_name, p2->short_name);
}

static GSList *dissector_plugins;

#ifdef HAVE_PLUGINS
void proto_register_plugin(const proto_plugin *plug)
{
    dissector_plugins = g_slist_prepend(dissector_plugins, (proto_plugin *)plug);
}
#else /* HAVE_PLUGINS */
//void proto_register_plugin(const proto_plugin *plug)
//{
//    printf("proto_register_plugin: built without support for binary plugins");
//}
#endif /* HAVE_PLUGINS */

//static void
//call_plugin_register_protoinfo(void *data, void *user_data)
//{
//    proto_plugin *plug = (proto_plugin *)data;

//    if (plug->register_protoinfo) {
//        plug->register_protoinfo();
//    }
//}

//static void call_plugin_register_handoff(void *data, void *user_data)
//{
//    proto_plugin *plug = (proto_plugin *)data;

//    if (plug->register_handoff) {
//        plug->register_handoff();
//    }
//}

/* initialize data structures and register protocols and fields */
void proto_init(GSList *register_all_plugin_protocols_list,
       GSList *register_all_plugin_handoffs_list)
{
    proto_cleanup_base();

    proto_names        = g_hash_table_new(g_str_hash, g_str_equal);
    proto_short_names  = g_hash_table_new(g_str_hash, g_str_equal);
    proto_filter_names = g_hash_table_new(g_str_hash, g_str_equal);

    proto_reserved_filter_names = g_hash_table_new(g_str_hash, g_str_equal);
    for (const char **ptr = reserved_filter_names; *ptr != NULL; ptr++) {
        /* GHashTable has no key destructor so the cast is safe. */
        g_hash_table_add(proto_reserved_filter_names, *(char **)ptr);
    }

    gpa_hfinfo.len           = 0;
    gpa_hfinfo.allocated_len = 0;
    gpa_hfinfo.hfi           = NULL;
    gpa_name_map             = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, save_same_name_hfinfo);
    gpa_protocol_aliases     = g_hash_table_new(g_str_hash, g_str_equal);
    deregistered_fields      = g_ptr_array_new();
    deregistered_data        = g_ptr_array_new();
    deregistered_slice       = g_ptr_array_new();

    /* Initialize the ftype subsystem */
    ftypes_initialize();

    /* Register one special-case FT_TEXT_ONLY field for use when
       converting wireshark to new-style proto_tree. These fields
       are merely strings on the GUI tree; they are not filterable */
    hf_text_only = proto_register_field_init(&hfi_text_only, -1);

    /* We've assigned all the subtree type values; allocate the array
       for them, and zero it out. */
    tree_is_expanded = calloc((num_tree_types/32)+1, sizeof(uint32_t));
}

static void proto_cleanup_base(void)
{
    protocol_t *protocol;
    header_field_info *hfinfo;

    /* Free the abbrev/ID hash table */
    if (gpa_name_map) {
        g_hash_table_destroy(gpa_name_map);
        gpa_name_map = NULL;
    }
    if (gpa_protocol_aliases) {
        g_hash_table_destroy(gpa_protocol_aliases);
        gpa_protocol_aliases = NULL;
    }
    g_free(last_field_name);
    last_field_name = NULL;

    while (protocols) {
        protocol = (protocol_t *)protocols->data;
        PROTO_REGISTRAR_GET_NTH(protocol->proto_id, hfinfo);
        DISSECTOR_ASSERT(protocol->proto_id == hfinfo->id);

        g_slice_free(header_field_info, hfinfo);
        if (protocol->parent_proto_id != -1) {
            // pino protocol
            DISSECTOR_ASSERT(protocol->fields == NULL); //helpers should not have any registered fields
        } else {
            if (protocol->fields) {
                g_ptr_array_free(protocol->fields, true);
            }
        }
        protocols = g_list_remove(protocols, protocol);
        g_free(protocol);
    }

    if (proto_names) {
        g_hash_table_destroy(proto_names);
        proto_names = NULL;
    }

    if (proto_short_names) {
        g_hash_table_destroy(proto_short_names);
        proto_short_names = NULL;
    }

    if (proto_filter_names) {
        g_hash_table_destroy(proto_filter_names);
        proto_filter_names = NULL;
    }

    if (proto_reserved_filter_names) {
        g_hash_table_destroy(proto_reserved_filter_names);
        proto_reserved_filter_names = NULL;
    }

    if (gpa_hfinfo.allocated_len) {
        gpa_hfinfo.len           = 0;
        gpa_hfinfo.allocated_len = 0;
        g_free(gpa_hfinfo.hfi);
        gpa_hfinfo.hfi           = NULL;
    }

    if (deregistered_fields) {
        g_ptr_array_free(deregistered_fields, true);
        deregistered_fields = NULL;
    }

    if (deregistered_data) {
        g_ptr_array_free(deregistered_data, true);
        deregistered_data = NULL;
    }

    if (deregistered_slice) {
        g_ptr_array_free(deregistered_slice, true);
        deregistered_slice = NULL;
    }

    g_free(tree_is_expanded);
    tree_is_expanded = NULL;

    if (prefixes)
        g_hash_table_destroy(prefixes);
}

void proto_cleanup(void)
{
    proto_free_deregistered_fields();
    proto_cleanup_base();

    g_slist_free(dissector_plugins);
    dissector_plugins = NULL;
}

// NOLINTNEXTLINE(misc-no-recursion)
static bool proto_tree_traverse_pre_order(proto_tree *tree, proto_tree_traverse_func func,
                  void *data)
{
    proto_node *pnode = tree;
    proto_node *child;
    proto_node *current;

    if (func(pnode, data))
        return true;

    child = pnode->first_child;
    while (child != NULL) {
        /*
         * The routine we call might modify the child, e.g. by
         * freeing it, so we get the child's successor before
         * calling that routine.
         */
        current = child;
        child   = current->next;
        // We recurse here, but we're limited by prefs.gui_max_tree_depth
        if (proto_tree_traverse_pre_order((proto_tree *)current, func, data))
            return true;
    }

    return false;
}

void proto_tree_children_foreach(proto_tree *tree, proto_tree_foreach_func func,
                void *data)
{
    proto_node *node = tree;
    proto_node *current;

    if (!node)
        return;

    node = node->first_child;
    while (node != NULL) {
        current = node;
        node    = current->next;
        func((proto_tree *)current, data);
    }
}

static void free_GPtrArray_value(void *key, void *value, void *user_data)
{
    GPtrArray         *ptrs = (GPtrArray *)value;
    int                hfid = GPOINTER_TO_UINT(key);
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    if (hfinfo->ref_type != HF_REF_TYPE_NONE) {
        /* when a field is referenced by a filter this also
           affects the refcount for the parent protocol so we need
           to adjust the refcount for the parent as well
        */
        if (hfinfo->parent != -1) {
            header_field_info *parent_hfinfo;
            PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);
            parent_hfinfo->ref_type = HF_REF_TYPE_NONE;
        }
        hfinfo->ref_type = HF_REF_TYPE_NONE;
    }

    g_ptr_array_free(ptrs, true);
}

static void proto_tree_free_node(proto_node *node, void *data)
{
    field_info *finfo  = PNODE_FINFO(node);

    proto_tree_children_foreach(node, proto_tree_free_node, NULL);

    fvalue_free(finfo->value);
    finfo->value = NULL;
}

void proto_tree_reset(proto_tree *tree)
{
    tree_data_t *tree_data = PTREE_DATA(tree);

    proto_tree_children_foreach(tree, proto_tree_free_node, NULL);

    /* free tree data */
    if (tree_data->interesting_hfids) {
        /* Free all the GPtrArray's in the interesting_hfids hash. */
        g_hash_table_foreach(tree_data->interesting_hfids,
            free_GPtrArray_value, NULL);

        /* And then remove all values. */
        g_hash_table_remove_all(tree_data->interesting_hfids);
    }

    /* Reset track of the number of children */
    tree_data->count = 0;

    PROTO_NODE_INIT(tree);
}

/* frees the resources that the dissection a proto_tree uses */
void proto_tree_free(proto_tree *tree)
{
    tree_data_t *tree_data = PTREE_DATA(tree);

    proto_tree_children_foreach(tree, proto_tree_free_node, NULL);

    /* free tree data */
    if (tree_data->interesting_hfids) {
        /* Free all the GPtrArray's in the interesting_hfids hash. */
        g_hash_table_foreach(tree_data->interesting_hfids,
            free_GPtrArray_value, NULL);

        /* And then destroy the hash. */
        g_hash_table_destroy(tree_data->interesting_hfids);
    }

    g_slice_free(tree_data_t, tree_data);

    g_slice_free(proto_tree, tree);
}

/* Is the parsing being done for a visible proto_tree or an invisible one?
 * By setting this correctly, the proto_tree creation is sped up by not
 * having to call vsnprintf and copy strings around.
 */
bool proto_tree_set_visible(proto_tree *tree, bool visible)
{
    bool old_visible = PTREE_DATA(tree)->visible;

    PTREE_DATA(tree)->visible = visible;

    return old_visible;
}

void proto_tree_set_fake_protocols(proto_tree *tree, bool fake_protocols)
{
    PTREE_DATA(tree)->fake_protocols = fake_protocols;
}

/* Assume dissector set only its protocol fields.
   This function is called by dissectors and allows the speeding up of filtering
   in wireshark; if this function returns false it is safe to reset tree to NULL
   and thus skip calling most of the expensive proto_tree_add_...()
   functions.
   If the tree is visible we implicitly assume the field is referenced.
*/
bool proto_field_is_referenced(proto_tree *tree, int proto_id)
{
    register header_field_info *hfinfo;


    if (!tree)
        return false;

    if (PTREE_DATA(tree)->visible)
        return true;

    PROTO_REGISTRAR_GET_NTH(proto_id, hfinfo);
    if (hfinfo->ref_type != HF_REF_TYPE_NONE)
        return true;

    if (hfinfo->type == FT_PROTOCOL && !PTREE_DATA(tree)->fake_protocols)
        return true;

    return false;
}


/* Finds a record in the hfinfo array by id. */
header_field_info *proto_registrar_get_nth(unsigned hfindex)
{
    register header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return hfinfo;
}


/*    Prefix initialization
 *      this allows for a dissector to register a display filter name prefix
 *      so that it can delay the initialization of the hf array as long as
 *      possible.
 */

/* compute a hash for the part before the dot of a display filter */
static unsigned prefix_hash (const void *key) {
    /* end the string at the dot and compute its hash */
    char* copy = g_strdup((const char *)key);
    char* c    = copy;
    unsigned tmp;

    for (; *c; c++) {
        if (*c == '.') {
            *c = 0;
            break;
        }
    }

    tmp = g_str_hash(copy);
    g_free(copy);
    return tmp;
}

/* are both strings equal up to the end or the dot? */
static gboolean prefix_equal (const void *ap, const void *bp) {
    const char* a = (const char *)ap;
    const char* b = (const char *)bp;

    do {
        char ac = *a++;
        char bc = *b++;

        if ( (ac == '.' || ac == '\0') &&   (bc == '.' || bc == '\0') ) return TRUE;

        if ( (ac == '.' || ac == '\0') && ! (bc == '.' || bc == '\0') ) return FALSE;
        if ( (bc == '.' || bc == '\0') && ! (ac == '.' || ac == '\0') ) return FALSE;

        if (ac != bc) return FALSE;
    } while (1);

    return FALSE;
}

/* Register a new prefix for "delayed" initialization of field arrays */
void proto_register_prefix(const char *prefix, prefix_initializer_t pi ) {
    if (! prefixes ) {
        prefixes = g_hash_table_new(prefix_hash, prefix_equal);
    }

    g_hash_table_insert(prefixes, (void *)prefix, (void *)pi);
}

/* helper to call all prefix initializers */
static gboolean initialize_prefix(void *k, void *v, void *u) {
    ((prefix_initializer_t)v)((const char *)k);
    return TRUE;
}

/** Initialize every remaining uninitialized prefix. */
void proto_initialize_all_prefixes(void)
{
    g_hash_table_foreach_remove(prefixes, initialize_prefix, NULL);
}

/* Finds a record in the hfinfo array by name.
 * If it fails to find it in the already registered fields,
 * it tries to find and call an initializer in the prefixes
 * table and if so it looks again.
 */

header_field_info *proto_registrar_get_byname(const char *field_name)
{
    header_field_info    *hfinfo;
    prefix_initializer_t  pi;

    if (!field_name)
        return NULL;

    if (g_strcmp0(field_name, last_field_name) == 0) {
        return last_hfinfo;
    }

    hfinfo = (header_field_info *)g_hash_table_lookup(gpa_name_map, field_name);

    if (hfinfo) {
        g_free(last_field_name);
        last_field_name = g_strdup(field_name);
        last_hfinfo = hfinfo;
        return hfinfo;
    }

    if (!prefixes)
        return NULL;

    if ((pi = (prefix_initializer_t)g_hash_table_lookup(prefixes, field_name) ) != NULL) {
        pi(field_name);
        g_hash_table_remove(prefixes, field_name);
    } else {
        return NULL;
    }

    hfinfo = (header_field_info *)g_hash_table_lookup(gpa_name_map, field_name);

    if (hfinfo) {
        g_free(last_field_name);
        last_field_name = g_strdup(field_name);
        last_hfinfo = hfinfo;
    }
    return hfinfo;
}

//header_field_info *proto_registrar_get_byalias(const char *alias_name)
//{
//    if (!alias_name) {
//        return NULL;
//    }

//    /* Find our aliased protocol. */
//    char *an_copy = g_strdup(alias_name);
//    char *dot = strchr(an_copy, '.');
//    if (dot) {
//        *dot = '\0';
//    }
//    const char *proto_pfx = (const char *) g_hash_table_lookup(gpa_protocol_aliases, an_copy);
//    if (!proto_pfx) {
//        g_free(an_copy);
//        return NULL;
//    }

//    /* Construct our aliased field and look it up. */
//    GString *filter_name = g_string_new(proto_pfx);
//    if (dot) {
//        g_string_append_printf(filter_name, ".%s", dot+1);
//    }
//    header_field_info *hfinfo = proto_registrar_get_byname(filter_name->str);
//    g_free(an_copy);
//    g_string_free(filter_name, TRUE);

//    return hfinfo;
//}

//int proto_registrar_get_id_byname(const char *field_name)
//{
//    header_field_info *hfinfo;

//    hfinfo = proto_registrar_get_byname(field_name);

//    if (!hfinfo)
//        return -1;

//    return hfinfo->id;
//}

static int label_strcat_flags(const header_field_info *hfinfo)
{
    if (FIELD_DISPLAY(hfinfo->display) & BASE_STR_WSP)
        return FORMAT_LABEL_REPLACE_SPACE;

    return 0;
}

static char *format_bytes_hfinfo_maxlen(const header_field_info *hfinfo,
    const uint8_t *bytes, unsigned length, size_t max_str_len)
{
    char *str = NULL;
    const uint8_t *p;
    bool is_printable;

    if (bytes) {
        if (hfinfo->display & BASE_SHOW_UTF_8_PRINTABLE) {
            /*
             * If all bytes are valid and printable UTF-8, show the
             * bytes as a string - in quotes to indicate that it's
             * a string.
             */
            if (isprint_utf8_string(bytes, length)) {
                str = malloc(256);
                snprintf(str, 256, "\"%.*s\"",
                         (int)length, bytes);
//                str = wmem_strdup_printf("\"%.*s\"",
//                    (int)length, bytes);
                return str;
            }
        } else if (hfinfo->display & BASE_SHOW_ASCII_PRINTABLE) {
            /*
             * Check whether all bytes are printable.
             */
            is_printable = true;
            for (p = bytes; p < bytes+length; p++) {
                if (!g_ascii_isprint(*p)) {
                    /* Not printable. */
                    is_printable = false;
                    break;
                }
            }

            /*
             * If all bytes are printable ASCII, show the bytes
             * as a string - in quotes to indicate that it's
             * a string.
             */
            if (is_printable) {
                str = malloc(256);
                snprintf(str, 256, "\"%.*s\"",
                         (int)length, bytes);
//                str = wmem_strdup_printf("\"%.*s\"",
//                    (int)length, bytes);
                return str;
            }
        }

        /*
         * Either it's not printable ASCII, or we don't care whether
         * it's printable ASCII; show it as hex bytes.
         */
        switch (FIELD_DISPLAY(hfinfo->display)) {
        case SEP_DOT:
            str = bytes_to_str_punct_maxlen(bytes, length, '.', max_str_len/3);
            break;
        case SEP_DASH:
            str = bytes_to_str_punct_maxlen(bytes, length, '-', max_str_len/3);
            break;
        case SEP_COLON:
            str = bytes_to_str_punct_maxlen(bytes, length, ':', max_str_len/3);
            break;
        case SEP_SPACE:
            str = bytes_to_str_punct_maxlen(bytes, length, ' ', max_str_len/3);
            break;
        case BASE_NONE:
        default:
            if (true/*prefs.display_byte_fields_with_spaces*/) {
                str = bytes_to_str_punct_maxlen(bytes, length, ' ', max_str_len/3);
            } else {
                str = bytes_to_str_maxlen(bytes, length, max_str_len/2);
            }
            break;
        }
    }
    else {
        if (hfinfo->display & BASE_ALLOW_ZERO) {
            str = strdup("<none>");
        } else {
            str = strdup("<MISSING>");
        }
    }
    return str;
}

static char *format_bytes_hfinfo(const header_field_info *hfinfo,
    const uint8_t *bytes, unsigned length)
{
    return format_bytes_hfinfo_maxlen(hfinfo, bytes, length, ITEM_LABEL_LENGTH);
}

/* Add a text-only node, leaving it to our caller to fill the text in */
static proto_item *proto_tree_add_text_node(proto_tree *tree, tvbuff_t *tvb, int start, int length)
{
    proto_item *pi;

    if (tree == NULL)
        return NULL;

    pi = proto_tree_add_pi(tree, &hfi_text_only, tvb, start, &length);

    return pi;
}

/* (INTERNAL USE ONLY) Add a text-only node to the proto_tree */
proto_item *proto_tree_add_text_internal(proto_tree *tree, tvbuff_t *tvb, int start, int length,
            const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;
    header_field_info *hfinfo;

    if (length == -1) {
        length = tvb_captured_length(tvb) ? tvb_ensure_captured_length_remaining(tvb, start) : 0;
    } else {
        tvb_ensure_bytes_exist(tvb, start, length);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hf_text_only, hfinfo);

    pi = proto_tree_add_text_node(tree, tvb, start, length);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

/* We could probably get away with changing is_error to a minimum length value. */
static void report_type_length_mismatch(proto_tree *tree, const char *descr, int length, bool is_error)
{
//    if (is_error) {
//        expert_add_info_format(NULL, tree, &ei_type_length_mismatch_error, "Trying to fetch %s with length %d", descr, length);
//    } else {
//        expert_add_info_format(NULL, tree, &ei_type_length_mismatch_warn, "Trying to fetch %s with length %d", descr, length);
//    }

//    if (is_error) {
//        THROW(ReportedBoundsError);
//    }
}

static uint32_t get_uint_value(proto_tree *tree, tvbuff_t *tvb, int offset, int length, const unsigned encoding)
{
    uint32_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_uint8(tvb, offset);
        if (encoding & ENC_ZIGBEE) {
            if (value == 0xFF) { /* Invalid Zigbee length, set to 0 */
                value = 0;
            }
        }
        break;
    case 2:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohs(tvb, offset)
                               : tvb_get_ntohs(tvb, offset);
        if (encoding & ENC_ZIGBEE) {
            if (value == 0xFFFF) { /* Invalid Zigbee length, set to 0 */
                value = 0;
            }
        }
        break;
    case 3:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh24(tvb, offset)
                               : tvb_get_ntoh24(tvb, offset);
        break;
    case 4:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                               : tvb_get_ntohl(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                                   : tvb_get_ntohl(tvb, offset);
        }
        report_type_length_mismatch(tree, "an unsigned integer", length, length_error);
        break;
    }
    return value;
}

static inline uint64_t get_uint64_value(proto_tree *tree, tvbuff_t *tvb, int offset, unsigned length, const unsigned encoding)
{
    uint64_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_uint8(tvb, offset);
        break;
    case 2:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohs(tvb, offset)
                               : tvb_get_ntohs(tvb, offset);
        break;
    case 3:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh24(tvb, offset)
                               : tvb_get_ntoh24(tvb, offset);
        break;
    case 4:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                               : tvb_get_ntohl(tvb, offset);
        break;
    case 5:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh40(tvb, offset)
                               : tvb_get_ntoh40(tvb, offset);
        break;
    case 6:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh48(tvb, offset)
                               : tvb_get_ntoh48(tvb, offset);
        break;
    case 7:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh56(tvb, offset)
                               : tvb_get_ntoh56(tvb, offset);
        break;
    case 8:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
                               : tvb_get_ntoh64(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
                                   : tvb_get_ntoh64(tvb, offset);
        }
        report_type_length_mismatch(tree, "an unsigned integer", length, length_error);
        break;
    }
    return value;
}

static int32_t get_int_value(proto_tree *tree, tvbuff_t *tvb, int offset, int length, const unsigned encoding)
{
    int32_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_int8(tvb, offset);
        break;
    case 2:
        value = encoding ? tvb_get_letohis(tvb, offset)
                 : tvb_get_ntohis(tvb, offset);
        break;
    case 3:
        value = encoding ? tvb_get_letohi24(tvb, offset)
                 : tvb_get_ntohi24(tvb, offset);
        break;
    case 4:
        value = encoding ? tvb_get_letohil(tvb, offset)
                 : tvb_get_ntohil(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = encoding ? tvb_get_letohil(tvb, offset)
                     : tvb_get_ntohil(tvb, offset);
        }
        report_type_length_mismatch(tree, "a signed integer", length, length_error);
        break;
    }
    return value;
}

/* Note: this returns an unsigned int64, but with the appropriate bit(s) set to
 * be cast-able as a int64_t. This is weird, but what the code has always done.
 */
static inline uint64_t get_int64_value(proto_tree *tree, tvbuff_t *tvb, int start, unsigned length, const unsigned encoding)
{
    uint64_t value = get_uint64_value(tree, tvb, start, length, encoding);

    switch (length) {
    case 7:
        value = ws_sign_ext64(value, 56);
        break;
    case 6:
        value = ws_sign_ext64(value, 48);
        break;
    case 5:
        value = ws_sign_ext64(value, 40);
        break;
    case 4:
        value = ws_sign_ext64(value, 32);
        break;
    case 3:
        value = ws_sign_ext64(value, 24);
        break;
    case 2:
        value = ws_sign_ext64(value, 16);
        break;
    case 1:
        value = ws_sign_ext64(value, 8);
        break;
    }

    return value;
}

/* For FT_STRING */
static inline const uint8_t *get_string_value(tvbuff_t *tvb, int start,
    int length, int *ret_length, const unsigned encoding)
{
    if (length == -1) {
        length = tvb_ensure_captured_length_remaining(tvb, start);
    }
    *ret_length = length;
    return tvb_get_string_enc(tvb, start, length, encoding);
}

/* For FT_STRINGZ */
static inline const uint8_t *get_stringz_value(proto_tree *tree, tvbuff_t *tvb,
    int start, int length, int *ret_length, const unsigned encoding)
{
    const uint8_t *value;

    if (length < -1) {
        report_type_length_mismatch(tree, "a string", length, true);
    }
    if (length == -1) {
        /* This can throw an exception */
        value = tvb_get_stringz_enc(tvb, start, &length, encoding);
    } else {
        /* In this case, length signifies the length of the string.
         *
         * This could either be a null-padded string, which doesn't
         * necessarily have a '\0' at the end, or a null-terminated
         * string, with a trailing '\0'.  (Yes, there are cases
         * where you have a string that's both counted and null-
         * terminated.)
         *
         * In the first case, we must allocate a buffer of length
         * "length+1", to make room for a trailing '\0'.
         *
         * In the second case, we don't assume that there is a
         * trailing '\0' there, as the packet might be malformed.
         * (XXX - should we throw an exception if there's no
         * trailing '\0'?)  Therefore, we allocate a buffer of
         * length "length+1", and put in a trailing '\0', just to
         * be safe.
         *
         * (XXX - this would change if we made string values counted
         * rather than null-terminated.)
         */
        value = tvb_get_string_enc(tvb, start, length, encoding);
    }
    *ret_length = length;
    return value;
}

/* For FT_UINT_STRING */
static inline const uint8_t *get_uint_string_value(proto_tree *tree,
    tvbuff_t *tvb, int start, int length, int *ret_length,
    const unsigned encoding)
{
    uint32_t n;
    const uint8_t *value;

    /* I believe it's ok if this is called with a NULL tree */
    n = get_uint_value(tree, tvb, start, length, encoding & ~ENC_CHARENCODING_MASK);
    value = tvb_get_string_enc(tvb, start + length, n, encoding);
    length += n;
    *ret_length = length;
    return value;
}

static void tree_data_add_maybe_interesting_field(tree_data_t *tree_data, field_info *fi)
{
    const header_field_info *hfinfo = fi->hfinfo;

    if (hfinfo->ref_type == HF_REF_TYPE_DIRECT || hfinfo->ref_type == HF_REF_TYPE_PRINT) {
        GPtrArray *ptrs = NULL;

        if (tree_data->interesting_hfids == NULL) {
            /* Initialize the hash because we now know that it is needed */
            tree_data->interesting_hfids =
                g_hash_table_new(g_direct_hash, NULL /* g_direct_equal */);
        } else if (g_hash_table_size(tree_data->interesting_hfids)) {
            ptrs = (GPtrArray *)g_hash_table_lookup(tree_data->interesting_hfids,
                       GINT_TO_POINTER(hfinfo->id));
        }

        if (!ptrs) {
            /* First element triggers the creation of pointer array */
            ptrs = g_ptr_array_new();
            g_hash_table_insert(tree_data->interesting_hfids,
                        GINT_TO_POINTER(hfinfo->id), ptrs);
        }

        g_ptr_array_add(ptrs, fi);
    }
}

/*
 * Validates that field length bytes are available starting from
 * start (pos/neg). Throws an exception if they aren't.
 */
static void test_length(header_field_info *hfinfo, tvbuff_t *tvb,
        int start, int length, const unsigned encoding)
{
    int size = length;

    if (!tvb)
        return;

    if ((hfinfo->type == FT_STRINGZ) ||
        ((encoding & ENC_VARINT_MASK) &&
         (FT_IS_UINT(hfinfo->type) || FT_IS_INT(hfinfo->type)))) {
        /* If we're fetching until the end of the TVB, only validate
         * that the offset is within range.
         */
        if (length == -1)
            size = 0;
    }

    tvb_ensure_bytes_exist(tvb, start, size);
}

static void detect_trailing_stray_characters(unsigned encoding, const char *string, int length, proto_item *pi)
{// TODO: maybe will delete it
    bool found_stray_character = false;

    if (!string)
        return;

    switch (encoding & ENC_CHARENCODING_MASK) {
        case ENC_ASCII:
        case ENC_UTF_8:
            for (int i = (int)strlen(string); i < length; i++) {
                if (string[i] != '\0') {
                    found_stray_character = true;
                    break;
                }
            }
            break;

        default:
            break;
    }

//    if (found_stray_character) {
//        expert_add_info(NULL, pi, &ei_string_trailing_characters);
//    }
}

static void free_fvalue_cb(void *data)
{
    fvalue_t *fv = (fvalue_t*)data;
    fvalue_free(fv);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it. */
static proto_item *proto_tree_new_item(field_info *new_fi, proto_tree *tree,
            tvbuff_t *tvb, int start, int length,
            unsigned encoding)
{
    proto_item *pi;
    uint32_t        value, n;
    uint64_t        value64;
//    ws_in4_addr ipv4_value;
    float        floatval;
    double        doubleval;
    const char *stringval = NULL;
//    nstime_t    time_stamp;
    bool        length_error;

    /* Ensure that the newly created fvalue_t is freed if we throw an
     * exception before adding it to the tree. (gcc creates clobbering
     * when it optimizes the equivalent TRY..EXCEPT implementation.)
     * XXX: Move the new_field_info() call inside here?
     */
//    CLEANUP_PUSH(free_fvalue_cb, new_fi->value);

    switch (new_fi->hfinfo->type) {
        case FT_NONE:
            /* no value to set for FT_NONE */
            break;

        case FT_PROTOCOL:
            proto_tree_set_protocol_tvb(new_fi, tvb, new_fi->hfinfo->name, length);
            break;

        case FT_BYTES:
            proto_tree_set_bytes_tvb(new_fi, tvb, start, length);
            break;

        case FT_UINT_BYTES:
            n = get_uint_value(tree, tvb, start, length, encoding);
            proto_tree_set_bytes_tvb(new_fi, tvb, start + length, n);

            /* Instead of calling proto_item_set_len(), since we don't yet
             * have a proto_item, we set the field_info's length ourselves. */
            new_fi->length = n + length;
            break;

        case FT_BOOLEAN:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_boolean(new_fi,
                get_uint64_value(tree, tvb, start, length, encoding));
            break;

        case FT_CHAR:
        /* XXX - make these just FT_UINT? */
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
            if (encoding & ENC_VARINT_MASK) {
                new_fi->length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value64, encoding);
                value = (uint32_t)value64;
                if (!(encoding & ENC_VARINT_QUIC)) {
                    new_fi->flags |= FI_VARINT;
                }
            }
            else {
                /*
                 * Map all non-zero values to little-endian for
                 * backwards compatibility.
                 */
                if (encoding)
                    encoding = ENC_LITTLE_ENDIAN;

                value = get_uint_value(tree, tvb, start, length, encoding);
            }
            proto_tree_set_uint(new_fi, value);
            break;

        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
            if (encoding & ENC_VARINT_MASK) {
                new_fi->length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value64, encoding);
                if (!(encoding & ENC_VARINT_QUIC)) {
                    new_fi->flags |= FI_VARINT;
                }
            }
            else {
                /*
                 * Map all other non-zero values to little-endian for
                 * backwards compatibility.
                 */
                if (encoding)
                    encoding = ENC_LITTLE_ENDIAN;

                value64 = get_uint64_value(tree, tvb, start, length, encoding);
            }
            proto_tree_set_uint64(new_fi, value64);
            break;

        /* XXX - make these just FT_INT? */
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_int(new_fi,
                get_int_value(tree, tvb, start, length, encoding));
            break;

        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_int64(new_fi,
                get_int64_value(tree, tvb, start, length, encoding));
            break;
#if 0
        case FT_IPv4:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_IPv4_LEN) {
                length_error = length < FT_IPv4_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPv4 address", length, length_error);
            }
            ipv4_value = tvb_get_ipv4(tvb, start);
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             */
            proto_tree_set_ipv4(new_fi, encoding ? GUINT32_SWAP_LE_BE(ipv4_value) : ipv4_value);
            break;

        case FT_IPXNET:
            if (length != FT_IPXNET_LEN) {
                length_error = length < FT_IPXNET_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPXNET address", length, length_error);
            }
            proto_tree_set_ipxnet(new_fi,
                get_uint_value(tree, tvb, start, FT_IPXNET_LEN, ENC_BIG_ENDIAN));
            break;

        case FT_IPv6:
            if (length != FT_IPv6_LEN) {
                length_error = length < FT_IPv6_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPv6 address", length, length_error);
            }
            proto_tree_set_ipv6_tvb(new_fi, tvb, start, length);
            break;

        case FT_FCWWN:
            if (length != FT_FCWWN_LEN) {
                length_error = length < FT_FCWWN_LEN ? true : false;
                report_type_length_mismatch(tree, "an FCWWN address", length, length_error);
            }
            proto_tree_set_fcwwn_tvb(new_fi, tvb, start, length);
            break;

        case FT_AX25:
            if (length != 7) {
                length_error = length < 7 ? true : false;
                report_type_length_mismatch(tree, "an AX.25 address", length, length_error);
            }
            proto_tree_set_ax25_tvb(new_fi, tvb, start);
            break;

        case FT_VINES:
            if (length != VINES_ADDR_LEN) {
                length_error = length < VINES_ADDR_LEN ? true : false;
                report_type_length_mismatch(tree, "a Vines address", length, length_error);
            }
            proto_tree_set_vines_tvb(new_fi, tvb, start);
            break;

        case FT_ETHER:
            if (length != FT_ETHER_LEN) {
                length_error = length < FT_ETHER_LEN ? true : false;
                report_type_length_mismatch(tree, "a MAC address", length, length_error);
            }
            proto_tree_set_ether_tvb(new_fi, tvb, start);
            break;

        case FT_EUI64:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_EUI64_LEN) {
                length_error = length < FT_EUI64_LEN ? true : false;
                report_type_length_mismatch(tree, "an EUI-64 address", length, length_error);
            }
            proto_tree_set_eui64_tvb(new_fi, tvb, start, encoding);
            break;
        case FT_GUID:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_GUID_LEN) {
                length_error = length < FT_GUID_LEN ? true : false;
                report_type_length_mismatch(tree, "a GUID", length, length_error);
            }
            proto_tree_set_guid_tvb(new_fi, tvb, start, encoding);
            break;

        case FT_OID:
        case FT_REL_OID:
            proto_tree_set_oid_tvb(new_fi, tvb, start, length);
            break;

        case FT_SYSTEM_ID:
            proto_tree_set_system_id_tvb(new_fi, tvb, start, length);
            break;
#endif
        case FT_FLOAT:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             *
             * At some point in the future, we might
             * support non-IEEE-binary floating-point
             * formats in the encoding as well
             * (IEEE decimal, System/3x0, VAX).
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 4) {
                length_error = length < 4 ? true : false;
                report_type_length_mismatch(tree, "a single-precision floating point number", length, length_error);
            }
            if (encoding)
                floatval = tvb_get_letohieee_float(tvb, start);
            else
                floatval = tvb_get_ntohieee_float(tvb, start);
            proto_tree_set_float(new_fi, floatval);
            break;

        case FT_DOUBLE:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             *
             * At some point in the future, we might
             * support non-IEEE-binary floating-point
             * formats in the encoding as well
             * (IEEE decimal, System/3x0, VAX).
             */
            if (encoding == true)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 8) {
                length_error = length < 8 ? true : false;
                report_type_length_mismatch(tree, "a double-precision floating point number", length, length_error);
            }
            if (encoding)
                doubleval = tvb_get_letohieee_double(tvb, start);
            else
                doubleval = tvb_get_ntohieee_double(tvb, start);
            proto_tree_set_double(new_fi, doubleval);
            break;

        case FT_STRING:
            stringval = get_string_value(tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;

        case FT_STRINGZ:
            stringval = get_stringz_value(tree, tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(),
             * since we don't yet have a proto_item, we
             * set the field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;

        case FT_UINT_STRING:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", if the
             * encoding value is true, treat that as
             * ASCII with a little-endian length.
             *
             * This won't work for code that passes
             * arbitrary non-zero values; that code
             * will need to be fixed.
             */
            if (encoding == true)
                encoding = ENC_ASCII|ENC_LITTLE_ENDIAN;
            stringval = get_uint_string_value(tree, tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;
#if 0
        case FT_STRINGZPAD:
//            stringval = get_stringzpad_value(
//                tvb, start, length, &length, encoding);
//            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
//            new_fi->length = length;
            break;

        case FT_STRINGZTRUNC:
//            stringval = get_stringztrunc_value(
//                tvb, start, length, &length, encoding);
//            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
//            new_fi->length = length;
            break;

        case FT_ABSOLUTE_TIME:
            /*
             * Absolute times can be in any of a number of
             * formats, and they can be big-endian or
             * little-endian.
             *
             * Historically FT_TIMEs were only timespecs;
             * the only question was whether they were stored
             * in big- or little-endian format.
             *
             * For backwards compatibility, we interpret an
             * encoding of 1 as meaning "little-endian timespec",
             * so that passing true is interpreted as that.
             */
//            if (encoding == true)
//                encoding = ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN;

//            get_time_value(tree, tvb, start, length, encoding, &time_stamp, false);

//            proto_tree_set_time(new_fi, &time_stamp);
            break;

        case FT_RELATIVE_TIME:
            /*
             * Relative times can be in any of a number of
             * formats, and they can be big-endian or
             * little-endian.
             *
             * Historically FT_TIMEs were only timespecs;
             * the only question was whether they were stored
             * in big- or little-endian format.
             *
             * For backwards compatibility, we interpret an
             * encoding of 1 as meaning "little-endian timespec",
             * so that passing true is interpreted as that.
             */
            if (encoding == true)
                encoding = ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN;

            get_time_value(tree, tvb, start, length, encoding, &time_stamp, true);

            proto_tree_set_time(new_fi, &time_stamp);
            break;
        case FT_IEEE_11073_SFLOAT:
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 2) {
                length_error = length < 2 ? true : false;
                report_type_length_mismatch(tree, "a IEEE 11073 SFLOAT", length, length_error);
            }

            fvalue_set_uinteger(new_fi->value, tvb_get_uint16(tvb, start, encoding));

            break;
        case FT_IEEE_11073_FLOAT:
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 4) {
                length_error = length < 4 ? true : false;
                report_type_length_mismatch(tree, "a IEEE 11073 FLOAT", length, length_error);
            }

            break;
#endif
        default:
            REPORT_DISSECTOR_BUG("field %s is of unknown type %d (%s)",
                         new_fi->hfinfo->abbrev,
                         new_fi->hfinfo->type,
                         ftype_name(new_fi->hfinfo->type));
            break;
    }
    FI_SET_FLAG(new_fi, (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN);

    /* Don't add new node to proto_tree until now so that any exceptions
     * raised by a tvbuff access method doesn't leave junk in the proto_tree. */
    /* XXX. wouldn't be better to add this item to tree, with some special
     * flag (FI_EXCEPTION?) to know which item caused exception? For
     * strings and bytes, we would have to set new_fi->value to something
     * non-NULL, or otherwise ensure that proto_item_fill_display_label
     * could handle NULL values. */
//    CLEANUP_POP
    pi = proto_tree_add_node(tree, new_fi);

    switch (new_fi->hfinfo->type) {

    case FT_STRING:
        /* XXX: trailing stray character detection should be done
             * _before_ conversion to UTF-8, because conversion can change
             * the length, or else get_string_length should return a value
             * for the "length in bytes of the string after conversion
             * including internal nulls." (Noting that we do, for other
             * reasons, still need the "length in bytes in the field",
             * especially for FT_STRINGZ.)
             *
             * This is true even for ASCII and UTF-8, because
             * substituting REPLACEMENT CHARACTERS for illegal characters
             * can also do so (and for UTF-8 possibly even make the
             * string _shorter_).
             */
        detect_trailing_stray_characters(encoding, stringval, length, pi);
        break;

    default:
        break;
    }

    return pi;
}

proto_item *proto_tree_add_item_ret_int(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                            const int start, int length,
                            const unsigned encoding, int32_t *retval)
{
    header_field_info *hfinfo;
    field_info      *new_fi;
    int32_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_INT8:
    case FT_INT16:
    case FT_INT24:
    case FT_INT32:
        break;
    case FT_INT64:
        REPORT_DISSECTOR_BUG("64-bit signed integer field %s used with proto_tree_add_item_ret_int()",
            hfinfo->abbrev);
        break;
    default:
        REPORT_DISSECTOR_BUG("Non-signed-integer field %s used with proto_tree_add_item_ret_int()",
            hfinfo->abbrev);
        break;
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    value = get_int_value(tree, tvb, start, length, encoding);

    if (retval) {
        int no_of_bits;
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= (uint32_t)(hfinfo->bitmask);
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
        no_of_bits = ws_count_ones(hfinfo->bitmask);
        *retval = ws_sign_ext32(*retval, no_of_bits);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_int(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_uint(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                             const int start, int length,
                             const unsigned encoding, uint32_t *retval)
{
    header_field_info *hfinfo;
    field_info      *new_fi;
    uint32_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_CHAR:
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_CHAR, FT_UINT8, FT_UINT16, FT_UINT24, or FT_UINT32",
            hfinfo->abbrev);
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    /* XXX - modify if we ever support EBCDIC FT_CHAR */
    if (encoding & ENC_VARINT_MASK) {
        uint64_t temp64;
        tvb_get_varint(tvb, start, length, &temp64, encoding);
        value = (uint32_t)temp64;
    } else {
        value = get_uint_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= (uint32_t)(hfinfo->bitmask);
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }
    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_uint64(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, uint64_t *retval)
{
    header_field_info *hfinfo;
    field_info      *new_fi;
    uint64_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_UINT40:
    case FT_UINT48:
    case FT_UINT56:
    case FT_UINT64:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT40, FT_UINT48, FT_UINT56, or FT_UINT64",
            hfinfo->abbrev);
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    if (encoding & ENC_VARINT_MASK) {
        tvb_get_varint(tvb, start, length, &value, encoding);
    } else {
        value = get_uint64_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= hfinfo->bitmask;
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_int64(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, int64_t *retval)
{
    header_field_info *hfinfo;
    field_info      *new_fi;
    int64_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_INT40:
    case FT_INT48:
    case FT_INT56:
    case FT_INT64:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_INT40, FT_INT48, FT_INT56, or FT_INT64",
            hfinfo->abbrev);
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    if (encoding & ENC_VARINT_MASK) {
        tvb_get_varint(tvb, start, length, &value, encoding);
    }
    else {
        value = get_int64_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_int64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_varint(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, uint64_t *retval, int *lenretval)
{
    header_field_info *hfinfo;
    field_info    *new_fi;
    uint64_t        value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    if ((!FT_IS_INT(hfinfo->type)) && (!FT_IS_UINT(hfinfo->type))) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT or FT_INT",
            hfinfo->abbrev);
    }

    /* length validation for native number encoding caught by get_uint64_value() */
    /* length has to be -1 or > 0 regardless of encoding */
    if (length == 0)
        REPORT_DISSECTOR_BUG("Invalid length %d passed to proto_tree_add_item_ret_varint",
            length);

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }

    length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value, encoding);

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= hfinfo->bitmask;
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    if (lenretval) {
        *lenretval = length;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);

}

proto_item *proto_tree_add_item_ret_boolean(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, bool *retval)
{
    header_field_info *hfinfo;
    field_info      *new_fi;
    uint64_t           value, bitval;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    if (hfinfo->type != FT_BOOLEAN) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_BOOLEAN",
            hfinfo->abbrev);
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = false;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    value = get_uint64_value(tree, tvb, start, length, encoding);

    if (retval) {
        bitval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            bitval &= hfinfo->bitmask;
        }
        *retval = (bitval != 0);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_boolean(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_float(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, float *retval)
{
    header_field_info *hfinfo = proto_registrar_get_nth(hfindex);
    field_info      *new_fi;
    float           value;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    if (hfinfo->type != FT_FLOAT) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_FLOAT", hfinfo->abbrev);
    }

    if (length != 4) {
        report_type_length_mismatch(tree, "a single-precision floating point number", length, true);
    }

    /* treat any nonzero encoding as little endian for backwards compatibility */
    value = encoding ? tvb_get_letohieee_float(tvb, start) : tvb_get_ntohieee_float(tvb, start);
    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);
    if (encoding) {
        new_fi->flags |= FI_LITTLE_ENDIAN;
    }

    proto_tree_set_float(new_fi, value);

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_double(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, double *retval)
{
    header_field_info *hfinfo = proto_registrar_get_nth(hfindex);
    field_info      *new_fi;
    double           value;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    if (hfinfo->type != FT_DOUBLE) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_DOUBLE", hfinfo->abbrev);
    }

    if (length != 8) {
        report_type_length_mismatch(tree, "a double-precision floating point number", length, true);
    }

    /* treat any nonzero encoding as little endian for backwards compatibility */
    value = encoding ? tvb_get_letohieee_double(tvb, start) : tvb_get_ntohieee_double(tvb, start);
    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);
    if (encoding) {
        new_fi->flags |= FI_LITTLE_ENDIAN;
    }

    proto_tree_set_double(new_fi, value);

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_string_and_length(proto_tree *tree, int hfindex,
                                          tvbuff_t *tvb,
                                          const int start, int length,
                                          const unsigned encoding,
                                          const uint8_t **retval,
                                          int *lenretval)
{
    proto_item *pi;
    header_field_info *hfinfo;
    field_info      *new_fi;
    const uint8_t      *value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_STRING:
        value = get_string_value(tvb, start, length, lenretval, encoding);
        break;
    case FT_STRINGZ:
        value = get_stringz_value(tree, tvb, start, length, lenretval, encoding);
        break;
    case FT_UINT_STRING:
        value = get_uint_string_value(tree, tvb, start, length, lenretval, encoding);
        break;
//    case FT_STRINGZPAD:
//        value = get_stringzpad_value(tvb, start, length, lenretval, encoding);
//        break;
//    case FT_STRINGZTRUNC:
//        value = get_stringztrunc_value(tvb, start, length, lenretval, encoding);
//        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_STRING, FT_STRINGZ, FT_UINT_STRING, FT_STRINGZPAD, or FT_STRINGZTRUNC",
            hfinfo->abbrev);
        break;
    }

    if (retval)
        *retval = value;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, *lenretval);

    proto_tree_set_string(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    pi = proto_tree_add_node(tree, new_fi);

    switch (hfinfo->type) {
    case FT_STRINGZ:
    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
    case FT_UINT_STRING:
        break;
    case FT_STRING:
        detect_trailing_stray_characters(encoding, value, length, pi);
        break;
    default:
        break;
    }

    return pi;
}

proto_item *proto_tree_add_item_ret_string(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                               const int start, int length,
                               const unsigned encoding,
                               const uint8_t **retval)
{
    return proto_tree_add_item_ret_string_and_length(tree, hfindex,
        tvb, start, length, encoding, retval, &length);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it. */
proto_item *proto_tree_add_item_new(proto_tree *tree, header_field_info *hfinfo, tvbuff_t *tvb,
            const int start, int length, const unsigned encoding)
{
    field_info        *new_fi;
    int          item_length;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    get_hfi_length(hfinfo, tvb, start, &length, &item_length, encoding);
    test_length(hfinfo, tvb, start, item_length, encoding);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, item_length);

    return proto_tree_new_item(new_fi, tree, tvb, start, length, encoding);
}

proto_item *proto_tree_add_item(proto_tree *tree, int hfindex, tvbuff_t *tvb,
            const int start, int length, const unsigned encoding)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return proto_tree_add_item_new(tree, hfinfo, tvb, start, length, encoding);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it.

   Return the length of the item through the pointer. */
proto_item *proto_tree_add_item_new_ret_length(proto_tree *tree, header_field_info *hfinfo,
                   tvbuff_t *tvb, const int start,
                   int length, const unsigned encoding,
                   int *lenretval)
{
    field_info        *new_fi;
    int          item_length;
    proto_item     *item;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    get_hfi_length(hfinfo, tvb, start, &length, &item_length, encoding);
    test_length(hfinfo, tvb, start, item_length, encoding);

    if (!tree) {
        /*
         * We need to get the correct item length here.
         * That's normally done by proto_tree_new_item(),
         * but we won't be calling it.
         */
        *lenretval = get_full_length(hfinfo, tvb, start, length,
            item_length, encoding);
        return NULL;
    }

//    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfinfo->id, hfinfo, {
//        /*
//         * Even if the tree item is not referenced (and thus faked),
//         * the caller must still be informed of the actual length.
//         */
//        *lenretval = get_full_length(hfinfo, tvb, start, length,
//            item_length, encoding);
//    });

    new_fi = new_field_info(tree, hfinfo, tvb, start, item_length);

    item = proto_tree_new_item(new_fi, tree, tvb, start, length, encoding);
    *lenretval = new_fi->length;
    return item;
}

proto_item *proto_tree_add_item_ret_length(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   const int start, int length,
                   const unsigned encoding, int *lenretval)
{
    register header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return proto_tree_add_item_new_ret_length(tree, hfinfo, tvb, start, length, encoding, lenretval);
}

/* which FT_ types can use proto_tree_add_bytes_item() */
static inline bool validate_proto_tree_add_bytes_ftype(const enum ftenum type)
{
    return (type == FT_BYTES      ||
        type == FT_UINT_BYTES ||
        type == FT_OID        ||
        type == FT_REL_OID    ||
        type == FT_SYSTEM_ID  );
}

/* Note: this does no validation that the byte array of an FT_OID or
   FT_REL_OID is actually valid; and neither does proto_tree_add_item(),
   so I think it's ok to continue not validating it?
 */
proto_item *proto_tree_add_bytes_item(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               const int start, int length, const unsigned encoding,
               GByteArray *retval, int *endoff, int *err)
{
    field_info      *new_fi;
    GByteArray      *bytes = retval;
    GByteArray      *created_bytes = NULL;
    bool       failed = false;
    uint32_t           n = 0;
    header_field_info *hfinfo;
    bool       generate = (bytes || tree) ? true : false;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    DISSECTOR_ASSERT_HINT(validate_proto_tree_add_bytes_ftype(hfinfo->type),
        "Called proto_tree_add_bytes_item but not a bytes-based FT_XXX type");

    CHECK_FOR_ZERO_OR_MINUS_LENGTH(length);

    if (encoding & ENC_STR_NUM) {
        REPORT_DISSECTOR_BUG("Decoding number strings for byte arrays is not supported");
    }

    if (generate && (encoding & ENC_STR_HEX)) {
        if (hfinfo->type == FT_UINT_BYTES) {
            /* can't decode FT_UINT_BYTES from strings */
            REPORT_DISSECTOR_BUG("proto_tree_add_bytes_item called for "
                "FT_UINT_BYTES type, but as ENC_STR_HEX");
        }

        if (!bytes) {
            /* caller doesn't care about return value, but we need it to
               call tvb_get_string_bytes() and set the tree later */
            bytes = created_bytes = g_byte_array_new();
        }

        /*
         * bytes might be NULL after this, but can't add expert
         * error until later; if it's NULL, just note that
         * it failed.
         */
        bytes = tvb_get_string_bytes(tvb, start, length, encoding, bytes, endoff);
        if (bytes == NULL)
            failed = true;
    }
    else if (generate) {
        tvb_ensure_bytes_exist(tvb, start, length);

        if (hfinfo->type == FT_UINT_BYTES) {
            n = length; /* n is now the "header" length */
            length = get_uint_value(tree, tvb, start, n, encoding);
            /* length is now the value's length; only store the value in the array */
            tvb_ensure_bytes_exist(tvb, start + n, length);
            if (!bytes) {
                /* caller doesn't care about return value, but
                 * we may need it to set the tree later */
                bytes = created_bytes = g_byte_array_new();
            }
            g_byte_array_append(bytes, tvb_get_ptr(tvb, start + n, length), length);
        }
        else if (length > 0) {
            if (!bytes) {
                /* caller doesn't care about return value, but
                 * we may need it to set the tree later */
                bytes = created_bytes = g_byte_array_new();
            }
            g_byte_array_append(bytes, tvb_get_ptr(tvb, start, length), length);
        }

        if (endoff)
            *endoff = start + n + length;
    }

    if (err)
        *err = failed ? EINVAL : 0;

    CHECK_FOR_NULL_TREE_AND_FREE(tree,
        {
            if (created_bytes)
            g_byte_array_free(created_bytes, true);
            created_bytes = NULL;
            bytes = NULL;
        } );

    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfinfo->id, hfinfo,
        {
            if (created_bytes)
            g_byte_array_free(created_bytes, true);
            created_bytes = NULL;
            bytes = NULL;
        } );

    /* n will be zero except when it's a FT_UINT_BYTES */
    new_fi = new_field_info(tree, hfinfo, tvb, start, n + length);

    if (encoding & ENC_STRING) {
//        if (failed)
//            expert_add_info(NULL, tree, &ei_byte_array_string_decoding_failed_error);

        if (bytes)
            proto_tree_set_bytes_gbytearray(new_fi, bytes);
        else
            proto_tree_set_bytes(new_fi, NULL, 0);

        if (created_bytes)
            g_byte_array_free(created_bytes, true);
    }
    else {
        /* n will be zero except when it's a FT_UINT_BYTES */
        proto_tree_set_bytes_tvb(new_fi, tvb, start + n, length);

        /* XXX: If we have a non-NULL tree but NULL retval, we don't
         * use the byte array created above in this case.
         */
        if (created_bytes)
            g_byte_array_free(created_bytes, true);

        FI_SET_FLAG(new_fi,
            (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN);
    }

    return proto_tree_add_node(tree, new_fi);
}

/* Add a FT_NONE to a proto_tree */
proto_item *proto_tree_add_none_format(proto_tree *tree, const int hfindex, tvbuff_t *tvb,
               const int start, int length, const char *format,
               ...)
{
    proto_item      *pi;
    va_list           ap;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_NONE);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    /* no value to set for FT_NONE */
    return pi;
}

static void proto_tree_set_protocol_tvb(field_info *fi, tvbuff_t *tvb, const char* field_data, int length)
{
    fvalue_set_protocol(fi->value, tvb, field_data, length);
}

/* Add a FT_PROTOCOL to a proto_tree */
proto_item *proto_tree_add_protocol_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, const char *format, ...)
{
    proto_item      *pi;
    tvbuff_t      *protocol_tvb;
    va_list           ap;
    header_field_info *hfinfo;
    char* protocol_rep;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_PROTOCOL);

    /*
     * This can throw an exception, so do it before we allocate anything.
     */
    protocol_tvb = (start == 0 ? tvb : tvb_new_subset_length(tvb, start, length));

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);

    va_start(ap, format);
    protocol_rep = strdup_vprintf(format, ap);
    proto_tree_set_protocol_tvb(PNODE_FINFO(pi), protocol_tvb, protocol_rep, length);
    g_free(protocol_rep);
    va_end(ap);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

/* Add a FT_BYTES to a proto_tree */
proto_item *proto_tree_add_bytes(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, const uint8_t *start_ptr)
{
    proto_item      *pi;
    header_field_info *hfinfo;
    int          item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &length, &item_length, ENC_NA);
    test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BYTES);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_bytes(PNODE_FINFO(pi), start_ptr, length);

    return pi;
}

/* Add a FT_BYTES to a proto_tree */
proto_item *proto_tree_add_bytes_with_length(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int tvbuff_length, const uint8_t *start_ptr, int ptr_length)
{
    proto_item    *pi;
    header_field_info *hfinfo;
    int           item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &tvbuff_length, &item_length, ENC_NA);
    test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BYTES);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &tvbuff_length);
    proto_tree_set_bytes(PNODE_FINFO(pi), start_ptr, ptr_length);

    return pi;
}

proto_item *proto_tree_add_bytes_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length,
                  const uint8_t *start_ptr,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    if (start_ptr == NULL)
        start_ptr = tvb_get_ptr(tvb, start, length);

    pi = proto_tree_add_bytes(tree, hfindex, tvb, start, length, start_ptr);

    TRY_TO_FAKE_THIS_REPR_NESTED(pi);

    va_start(ap, format);
    proto_tree_set_representation_value(pi, format, ap);
    va_end(ap);

    return pi;
}

proto_item *proto_tree_add_bytes_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, const uint8_t *start_ptr,
                const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    if (start_ptr == NULL)
        start_ptr = tvb_get_ptr(tvb, start, length);

    pi = proto_tree_add_bytes(tree, hfindex, tvb, start, length, start_ptr);

    TRY_TO_FAKE_THIS_REPR_NESTED(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

static void
proto_tree_set_bytes(field_info *fi, const uint8_t* start_ptr, int length)
{
    DISSECTOR_ASSERT(length >= 0);
    DISSECTOR_ASSERT(start_ptr != NULL || length == 0);

    fvalue_set_bytes_data(fi->value, start_ptr, length);
}


static void
proto_tree_set_bytes_tvb(field_info *fi, tvbuff_t *tvb, int offset, int length)
{
    tvb_ensure_bytes_exist(tvb, offset, length);
    proto_tree_set_bytes(fi, tvb_get_ptr(tvb, offset, length), length);
}

static void proto_tree_set_bytes_gbytearray(field_info *fi, const GByteArray *value)
{
    GByteArray *bytes;

    DISSECTOR_ASSERT(value != NULL);

    bytes = byte_array_dup(value);

    fvalue_set_byte_array(fi->value, bytes);
}

/* Add a FT_STRING, FT_STRINGZ, FT_STRINGZPAD, or FT_STRINGZTRUNC to a
 * proto_tree. Creates own copy of string, and frees it when the proto_tree
 * is destroyed. */
proto_item *proto_tree_add_string(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, const char* value)
{
    proto_item      *pi;
    header_field_info *hfinfo;
    int          item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &length, &item_length, ENC_NA);
    /*
     * Special case - if the length is 0, skip the test, so that
     * we can have an empty string right after the end of the
     * packet.  (This handles URL-encoded forms where the last field
     * has no value so the form ends right after the =.)
     */
    if (item_length != 0)
        test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE_IS_STRING(hfinfo);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    DISSECTOR_ASSERT(length >= 0);

//    WS_UTF_8_CHECK(value, -1);
    proto_tree_set_string(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_string_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, const char* value,
                   const char *format,
                   ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_string(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_string_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, const char* value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_string(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_STRING value */
static void proto_tree_set_string(field_info *fi, const char* value)
{
    if (value) {
        fvalue_set_string(fi->value, value);
    } else {
        /*
         * XXX - why is a null value for a string field
         * considered valid?
         */
        fvalue_set_string(fi->value, "[ Null ]");
    }
}

/* Add a FT_BOOLEAN to a proto_tree */
proto_item *proto_tree_add_boolean(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
               int length, uint64_t value)
{
    proto_item      *pi;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BOOLEAN);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_boolean(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_boolean_format_value(proto_tree *tree, int hfindex,
                    tvbuff_t *tvb, int start, int length,
                    uint64_t value, const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_boolean(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_boolean_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, uint64_t value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_boolean(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_BOOLEAN value */
static void proto_tree_set_boolean(field_info *fi, uint64_t value)
{
    proto_tree_set_uint64(fi, value);
}

/* Generate, into "buf", a string showing the bits of a bitfield.
   Return a pointer to the character after that string. */
static char *other_decode_bitfield_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    int i = 0;
    uint64_t bit;
    char *p;

    p = buf;

    /* This is a devel error. It is safer to stop here. */
    DISSECTOR_ASSERT(width >= 1);

    bit = UINT64_C(1) << (width - 1);
    for (;;) {
        if (mask & bit) {
            /* This bit is part of the field.  Show its value. */
            if (val & bit)
                *p++ = '1';
            else
                *p++ = '0';
        } else {
            /* This bit is not part of the field. */
            *p++ = '.';
        }
        bit >>= 1;
        i++;
        if (i >= width)
            break;
        if (i % 4 == 0)
            *p++ = ' ';
    }
    *p = '\0';
    return p;
}

static char *decode_bitfield_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    char *p;

    p = other_decode_bitfield_value(buf, val, mask, width);
    p = g_stpcpy(p, " = ");

    return p;
}

static char *other_decode_bitfield_varint_value(char *buf, uint64_t val, uint64_t mask, const int width)
{
    int i = 0;
    uint64_t bit;
    char *p;

    p = buf;

    /* This is a devel error. It is safer to stop here. */
    DISSECTOR_ASSERT(width >= 1);

    bit = UINT64_C(1) << (width - 1);
    for (;;) {
        if (((8-(i % 8)) != 8) && /* MSB is never used for value. */
            (mask & bit)) {
            /* This bit is part of the field.  Show its value. */
            if (val & bit)
                *p++ = '1';
            else
                *p++ = '0';
        } else {
            /* This bit is not part of the field. */
            *p++ = '.';
        }
        bit >>= 1;
        i++;
        if (i >= width)
            break;
        if (i % 4 == 0)
            *p++ = ' ';
    }

    *p = '\0';
    return p;
}

static char *decode_bitfield_varint_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    char *p;

    p = other_decode_bitfield_varint_value(buf, val, mask, width);
    p = g_stpcpy(p, " = ");

    return p;
}

/* Add a FT_FLOAT to a proto_tree */
proto_item *proto_tree_add_float(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, float value)
{
    proto_item      *pi;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_FLOAT);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_float(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_float_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, float value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_float(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_float_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, float value,
                const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_float(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_FLOAT value */
static void proto_tree_set_float(field_info *fi, float value)
{
    fvalue_set_floating(fi->value, value);
}

/* Add a FT_DOUBLE to a proto_tree */
proto_item *proto_tree_add_double(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, double value)
{
    proto_item      *pi;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_DOUBLE);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_double(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_double_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, double value,
                   const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_double(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_double_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, double value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_double(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_DOUBLE value */
static void proto_tree_set_double(field_info *fi, double value)
{
    fvalue_set_floating(fi->value, value);
}

/* Add FT_CHAR or FT_UINT{8,16,24,32} to a proto_tree */
proto_item *proto_tree_add_uint(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
            int length, uint32_t value)
{
    proto_item      *pi = NULL;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
        case FT_CHAR:
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
        case FT_FRAMENUM:
            pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
            proto_tree_set_uint(PNODE_FINFO(pi), value);
            break;

        default:
            REPORT_DISSECTOR_BUG("field %s is not of type FT_CHAR, FT_UINT8, FT_UINT16, FT_UINT24, FT_UINT32, or FT_FRAMENUM",
                hfinfo->abbrev);
    }

    return pi;
}

proto_item *proto_tree_add_uint_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, uint32_t value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_uint_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               int start, int length, uint32_t value,
               const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_UINT{8,16,24,32} value */
static void proto_tree_set_uint(field_info *fi, uint32_t value)
{
    const header_field_info *hfinfo;
    uint32_t           integer;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= (uint32_t)(hfinfo->bitmask);

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_uinteger(fi->value, integer);
}

/* Add FT_UINT{40,48,56,64} to a proto_tree */
proto_item *proto_tree_add_uint64(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, uint64_t value)
{
    proto_item      *pi = NULL;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
        case FT_FRAMENUM:
            pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
            proto_tree_set_uint64(PNODE_FINFO(pi), value);
            break;

        default:
            REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT40, FT_UINT48, FT_UINT56, FT_UINT64, or FT_FRAMENUM",
                hfinfo->abbrev);
    }

    return pi;
}

proto_item *proto_tree_add_uint64_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, uint64_t value,
                   const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_uint64_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, uint64_t value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_UINT{40,48,56,64} value */
static void proto_tree_set_uint64(field_info *fi, uint64_t value)
{
    const header_field_info *hfinfo;
    uint64_t           integer;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= hfinfo->bitmask;

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_uinteger64(fi->value, integer);
}

/* Add FT_INT{8,16,24,32} to a proto_tree */
proto_item *proto_tree_add_int(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
           int length, int32_t value)
{
    proto_item      *pi = NULL;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
            pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
            proto_tree_set_int(PNODE_FINFO(pi), value);
            break;

        default:
            REPORT_DISSECTOR_BUG("field %s is not of type FT_INT8, FT_INT16, FT_INT24, or FT_INT32",
                hfinfo->abbrev);
        break;
    }

    return pi;
}

proto_item *proto_tree_add_int_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, int32_t value,
                const char *format, ...)
{
    proto_item  *pi;
    va_list         ap;

    pi = proto_tree_add_int(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_int_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
              int start, int length, int32_t value,
              const char *format, ...)
{
    proto_item *pi;
    va_list     ap;

    pi = proto_tree_add_int(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_INT{8,16,24,32} value */
static void proto_tree_set_int(field_info *fi, int32_t value)
{
    const header_field_info *hfinfo;
    uint32_t           integer;
    int           no_of_bits;

    hfinfo = fi->hfinfo;
    integer = (uint32_t) value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= (uint32_t)(hfinfo->bitmask);

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        no_of_bits = ws_count_ones(hfinfo->bitmask);
        integer = ws_sign_ext32(integer, no_of_bits);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_sinteger(fi->value, integer);
}

/* Add FT_INT{40,48,56,64} to a proto_tree */
proto_item *proto_tree_add_int64(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, int64_t value)
{
    proto_item      *pi = NULL;
    header_field_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
            pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
            proto_tree_set_int64(PNODE_FINFO(pi), value);
            break;

        default:
            REPORT_DISSECTOR_BUG("field %s is not of type FT_INT40, FT_INT48, FT_INT56, or FT_INT64",
                hfinfo->abbrev);
    }

    return pi;
}

proto_item *proto_tree_add_int64_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, int64_t value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_int64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_INT{40,48,56,64} value */
static void proto_tree_set_int64(field_info *fi, int64_t value)
{
    const header_field_info *hfinfo;
    uint64_t           integer;
    int           no_of_bits;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= hfinfo->bitmask;

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        no_of_bits = ws_count_ones(hfinfo->bitmask);
        integer = ws_sign_ext64(integer, no_of_bits);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_sinteger64(fi->value, integer);
}

proto_item *proto_tree_add_int64_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               int start, int length, int64_t value,
               const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_int64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Add a field_info struct to the proto_tree, encapsulating it in a proto_node */
static proto_item *proto_tree_add_node(proto_tree *tree, field_info *fi)
{
    proto_node *pnode, *tnode, *sibling;
    field_info *tfi;
    unsigned depth = 1;

    /*
     * Restrict our depth. proto_tree_traverse_pre_order and
     * proto_tree_traverse_post_order (and possibly others) are recursive
     * so we need to be mindful of our stack size.
     */
    if (tree->first_child == NULL) {
        for (tnode = tree; tnode != NULL; tnode = tnode->parent) {
            depth++;
            // TODO: FIX max_tree_depth
            if (depth > 32) {
                fvalue_free(fi->value);
                fi->value = NULL;
            }
//            if (G_UNLIKELY(depth > prefs.gui_max_tree_depth)) {
//                fvalue_free(fi->value);
//                fi->value = NULL;
//                THROW_MESSAGE(DissectorError, wmem_strdup_printf(
//                             "Maximum tree depth %d exceeded for \"%s\" - \"%s\" (%s:%u) (Maximum depth can be increased in advanced preferences)",
//                             prefs.gui_max_tree_depth,
//                             fi->hfinfo->name, fi->hfinfo->abbrev, G_STRFUNC, __LINE__));
//            }
        }
    }

    /*
     * Make sure "tree" is ready to have subtrees under it, by
     * checking whether it's been given an ett_ value.
     *
     * "PNODE_FINFO(tnode)" may be null; that's the case for the root
     * node of the protocol tree.  That node is not displayed,
     * so it doesn't need an ett_ value to remember whether it
     * was expanded.
     */
    tnode = tree;
    tfi = PNODE_FINFO(tnode);
    if (tfi != NULL && (tfi->tree_type < 0 || tfi->tree_type >= num_tree_types)) {
        /* Since we are not adding fi to a node, its fvalue won't get
         * freed by proto_tree_free_node(), so free it now.
         */
        fvalue_free(fi->value);
        fi->value = NULL;
        REPORT_DISSECTOR_BUG("\"%s\" - \"%s\" tfi->tree_type: %d invalid (%s:%u)",
                     fi->hfinfo->name, fi->hfinfo->abbrev, tfi->tree_type, __FILE__, __LINE__);
        /* XXX - is it safe to continue here? */
    }

    pnode = wmem_new(proto_node);
    PROTO_NODE_INIT(pnode);
    pnode->parent = tnode;
    PNODE_FINFO(pnode) = fi;
    pnode->tree_data = PTREE_DATA(tree);

    if (tnode->last_child != NULL) {
        sibling = tnode->last_child;
        DISSECTOR_ASSERT(sibling->next == NULL);
        sibling->next = pnode;
    } else
        tnode->first_child = pnode;
    tnode->last_child = pnode;

    tree_data_add_maybe_interesting_field(pnode->tree_data, fi);

    return (proto_item *)pnode;
}

/* Generic way to allocate field_info and add to proto_tree.
 * Sets *pfi to address of newly-allocated field_info struct */
static proto_item *proto_tree_add_pi(proto_tree *tree, header_field_info *hfinfo, tvbuff_t *tvb, int start,
          int *length)
{
    proto_item *pi;
    field_info *fi;
    int        item_length;

    get_hfi_length(hfinfo, tvb, start, length, &item_length, ENC_NA);
    fi = new_field_info(tree, hfinfo, tvb, start, item_length);
    pi = proto_tree_add_node(tree, fi);

    return pi;
}


static void get_hfi_length(header_field_info *hfinfo, tvbuff_t *tvb, const int start, int *length,
           int *item_length, const unsigned encoding)
{
    int length_remaining;

    /*
     * We only allow a null tvbuff if the item has a zero length,
     * i.e. if there's no data backing it.
     */
    DISSECTOR_ASSERT(tvb != NULL || *length == 0);

    /*
     * XXX - in some protocols, there are 32-bit unsigned length
     * fields, so lengths in protocol tree and tvbuff routines
     * should really be unsigned.  We should have, for those
     * field types for which "to the end of the tvbuff" makes sense,
     * additional routines that take no length argument and
     * add fields that run to the end of the tvbuff.
     */
    if (*length == -1) {
        /*
         * For FT_NONE, FT_PROTOCOL, FT_BYTES, FT_STRING,
         * FT_STRINGZPAD, and FT_STRINGZTRUNC fields, a length
         * of -1 means "set the length to what remains in the
         * tvbuff".
         *
         * The assumption is either that
         *
         *    1) the length of the item can only be determined
         *       by dissection (typically true of items with
         *       subitems, which are probably FT_NONE or
         *       FT_PROTOCOL)
         *
         * or
         *
         *    2) if the tvbuff is "short" (either due to a short
         *       snapshot length or due to lack of reassembly of
         *       fragments/segments/whatever), we want to display
         *       what's available in the field (probably FT_BYTES
         *       or FT_STRING) and then throw an exception later
         *
         * or
         *
         *    3) the field is defined to be "what's left in the
         *       packet"
         *
         * so we set the length to what remains in the tvbuff so
         * that, if we throw an exception while dissecting, it
         * has what is probably the right value.
         *
         * For FT_STRINGZ, it means "the string is null-terminated,
         * not null-padded; set the length to the actual length
         * of the string", and if the tvbuff if short, we just
         * throw an exception.
         *
         * For ENC_VARINT_PROTOBUF|ENC_VARINT_QUIC|ENC_VARIANT_ZIGZAG|ENC_VARINT_SDNV, it means "find the end of the string",
         * and if the tvbuff if short, we just throw an exception.
         *
         * It's not valid for any other type of field.  For those
         * fields, we treat -1 the same way we treat other
         * negative values - we assume the length is a Really
         * Big Positive Number, and throw a ReportedBoundsError
         * exception, under the assumption that the Really Big
         * Length would run past the end of the packet.
         */
        if ((FT_IS_INT(hfinfo->type)) || (FT_IS_UINT(hfinfo->type))) {
            if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
                /*
                 * Leave the length as -1, so our caller knows
                 * it was -1.
                 */
                *item_length = *length;
                return;
            } else if (encoding & ENC_VARINT_QUIC) {
                switch (tvb_get_uint8(tvb, start) >> 6)
                {
                case 0: /* 0b00 => 1 byte length (6 bits Usable) */
                    *item_length = 1;
                    break;
                case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
                    *item_length = 2;
                    break;
                case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
                    *item_length = 4;
                    break;
                case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
                    *item_length = 8;
                    break;
                }
            }
        }

        switch (hfinfo->type) {

        case FT_PROTOCOL:
        case FT_NONE:
        case FT_BYTES:
        case FT_STRING:
        case FT_STRINGZPAD:
        case FT_STRINGZTRUNC:
            /*
             * We allow FT_PROTOCOLs to be zero-length -
             * for example, an ONC RPC NULL procedure has
             * neither arguments nor reply, so the
             * payload for that protocol is empty.
             *
             * We also allow the others to be zero-length -
             * because that's the way the code has been for a
             * long, long time.
             *
             * However, we want to ensure that the start
             * offset is not *past* the byte past the end
             * of the tvbuff: we throw an exception in that
             * case.
             */
            *length = tvb_captured_length(tvb) ? tvb_ensure_captured_length_remaining(tvb, start) : 0;
            DISSECTOR_ASSERT(*length >= 0);
            break;

        case FT_STRINGZ:
            /*
             * Leave the length as -1, so our caller knows
             * it was -1.
             */
            break;

        default:
//            THROW(ReportedBoundsError);
            DISSECTOR_ASSERT_NOT_REACHED();
            return;
            break;
        }
        *item_length = *length;
    } else {
        *item_length = *length;
        if (hfinfo->type == FT_PROTOCOL || hfinfo->type == FT_NONE) {
            /*
             * These types are for interior nodes of the
             * tree, and don't have data associated with
             * them; if the length is negative (XXX - see
             * above) or goes past the end of the tvbuff,
             * cut it short at the end of the tvbuff.
             * That way, if this field is selected in
             * Wireshark, we don't highlight stuff past
             * the end of the data.
             */
            /* XXX - what to do, if we don't have a tvb? */
            if (tvb) {
                length_remaining = tvb_captured_length_remaining(tvb, start);
                if (*item_length < 0 ||
                    (*item_length > 0 &&
                      (length_remaining < *item_length)))
                    *item_length = length_remaining;
            }
        }
        if (*item_length < 0) {
//            THROW(ReportedBoundsError);
            return;
        }
    }
}

static int get_full_length(header_field_info *hfinfo, tvbuff_t *tvb, const int start,
        int length, unsigned item_length, const int encoding)
{
    uint32_t n;

    /*
     * We need to get the correct item length here.
     * That's normally done by proto_tree_new_item(),
     * but we won't be calling it.
     */
    switch (hfinfo->type) {

    case FT_NONE:
    case FT_PROTOCOL:
    case FT_BYTES:
        /*
         * The length is the specified length.
         */
        break;

    case FT_UINT_BYTES:
        n = get_uint_value(NULL, tvb, start, length, encoding);
        item_length += n;
        if ((int)item_length < length) {
//            THROW(ReportedBoundsError);
        }
        break;

    /* XXX - make these just FT_UINT? */
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
    case FT_UINT40:
    case FT_UINT48:
    case FT_UINT56:
    case FT_UINT64:
    /* XXX - make these just FT_INT? */
    case FT_INT8:
    case FT_INT16:
    case FT_INT24:
    case FT_INT32:
    case FT_INT40:
    case FT_INT48:
    case FT_INT56:
    case FT_INT64:
        if (encoding & ENC_VARINT_MASK) {
            if (length < -1) {
                report_type_length_mismatch(NULL, "a FT_[U]INT", length, true);
            }
            if (length == -1) {
                uint64_t dummy;
                /* This can throw an exception */
                /* XXX - do this without fetching the varint? */
                length = tvb_get_varint(tvb, start, FT_VARINT_MAX_LEN, &dummy, encoding);
                if (length == 0) {
//                    THROW(ReportedBoundsError);
                }
            }
            item_length = length;
            break;
        }

        /*
         * The length is the specified length.
         */
        break;

    case FT_BOOLEAN:
    case FT_CHAR:
    case FT_IPv4:
    case FT_IPXNET:
    case FT_IPv6:
    case FT_FCWWN:
    case FT_AX25:
    case FT_VINES:
    case FT_ETHER:
    case FT_EUI64:
    case FT_GUID:
    case FT_OID:
    case FT_REL_OID:
    case FT_SYSTEM_ID:
    case FT_FLOAT:
    case FT_DOUBLE:
    case FT_STRING:
        /*
         * The length is the specified length.
         */
        break;

    case FT_STRINGZ:
        if (length < -1) {
            report_type_length_mismatch(NULL, "a string", length, true);
        }
        if (length == -1) {
            /* This can throw an exception */
            /* XXX - do this without fetching the string? */
            wmem_free(tvb_get_stringz_enc(tvb, start, &length, encoding));
        }
        item_length = length;
        break;

    case FT_UINT_STRING:
        n = get_uint_value(NULL, tvb, start, length, encoding & ~ENC_CHARENCODING_MASK);
        item_length += n;
        if ((int)item_length < length) {
//            THROW(ReportedBoundsError);
        }
        break;

    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
    case FT_ABSOLUTE_TIME:
    case FT_RELATIVE_TIME:
    case FT_IEEE_11073_SFLOAT:
    case FT_IEEE_11073_FLOAT:
        /*
         * The length is the specified length.
         */
        break;

    default:
        REPORT_DISSECTOR_BUG("field %s has type %d (%s) not handled in gset_full_length()",
                     hfinfo->abbrev,
                     hfinfo->type,
                     ftype_name(hfinfo->type));
        break;
    }
    return item_length;
}

static field_info *new_field_info(proto_tree *tree, header_field_info *hfinfo, tvbuff_t *tvb,
           const int start, const int item_length)
{
    field_info *fi;

    FIELD_INFO_NEW(fi);

    fi->hfinfo     = hfinfo;
    fi->start      = start;
    fi->start     += (tvb) ? tvb_raw_offset(tvb) : 0;
    fi->length     = item_length;
    fi->tree_type  = -1;
    fi->flags      = 0;

    if (!PTREE_DATA(tree)->visible) {
        /* If the tree is not visible, set the item hidden, unless we
         * need the representation or length and can't fake them.
         */
        if (hfinfo->ref_type != HF_REF_TYPE_PRINT && (hfinfo->type != FT_PROTOCOL || PTREE_DATA(tree)->fake_protocols)) {
            FI_SET_FLAG(fi, FI_HIDDEN);
        }
    }

    fi->value = fvalue_new(fi->hfinfo->type);
    fi->rep        = NULL;

    /* add the data source tvbuff */
    fi->ds_tvb = tvb ? tvb_get_ds_tvb(tvb) : NULL;

    fi->appendix_start  = 0;
    fi->appendix_length = 0;

    fi->total_layer_num = tree->tree_data->pinfo->curr_layer_num;
    fi->proto_layer_num = tree->tree_data->pinfo->curr_proto_layer_num;

    return fi;
}

/* If the protocol tree is to be visible, set the representation of a
   proto_tree entry with the name of the field for the item and with
   the value formatted with the supplied printf-style format and
   argument list. */
static void proto_tree_set_representation_value(proto_item *pi, const char *format, va_list ap)
{
    /* If the tree (GUI) or item isn't visible it's pointless for us to generate the protocol
     * items string representation */
    if (PTREE_DATA(pi)->visible && !proto_item_is_hidden(pi)) {
        size_t            name_pos, ret = 0;
        char              *str;
        field_info        *fi = PITEM_FINFO(pi);
        const header_field_info *hf;

        DISSECTOR_ASSERT(fi);

        hf = fi->hfinfo;

        ITEM_LABEL_NEW(fi->rep);
        if (hf->bitmask && (hf->type == FT_BOOLEAN || FT_IS_UINT(hf->type))) {
            uint64_t val;
            char *p;

            if (FT_IS_UINT32(hf->type))
                val = fvalue_get_uinteger(fi->value);
            else
                val = fvalue_get_uinteger64(fi->value);

            val <<= hfinfo_bitshift(hf);

            p = decode_bitfield_value(fi->rep->representation, val, hf->bitmask, hfinfo_container_bitwidth(hf));
            ret = (p - fi->rep->representation);
        }

        /* put in the hf name */
        name_pos = ret = label_concat(fi->rep->representation, ret, hf->name);

        ret = label_concat(fi->rep->representation, ret, ": ");
        /* If possible, Put in the value of the string */
        str = strdup_vprintf(format, ap);
//        WS_UTF_8_CHECK(str, -1);
        ret = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, ret, str, 0);
        if (ret >= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            label_mark_truncated(fi->rep->representation, name_pos);
        }
    }
}

/* If the protocol tree is to be visible, set the representation of a
   proto_tree entry with the representation formatted with the supplied
   printf-style format and argument list. */
static void proto_tree_set_representation(proto_item *pi, const char *format, va_list ap)
{
    size_t        ret;    /*tmp return value */
    char       *str;
    field_info *fi = PITEM_FINFO(pi);

    DISSECTOR_ASSERT(fi);

    if (!proto_item_is_hidden(pi)) {
        ITEM_LABEL_NEW(fi->rep);

        str = strdup_vprintf(format, ap);
//        WS_UTF_8_CHECK(str, -1);
        ret = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, 0, str, 0);
        if (ret >= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            LABEL_MARK_TRUNCATED_START(fi->rep->representation);
        }
    }
}

static int protol_strlcpy(char *dest, const char *src, size_t dest_size)
{
    if (dest_size == 0) return 0;

    size_t res = g_strlcpy(dest, src, dest_size);

    /* At most dest_size - 1 characters will be copied
     * (unless dest_size is 0). */
    if (res >= dest_size)
        res = dest_size - 1;
    return (int) res;
}

int proto_item_fill_display_label(const field_info *finfo, char *display_label_str, const int label_str_size)
{
    const header_field_info *hfinfo = finfo->hfinfo;
    int label_len = 0;
    char *tmp_str;
    const char *str;
    const uint8_t *bytes;
    uint32_t number;
    uint64_t number64;
    const char *hf_str_val;
    char number_buf[NUMBER_LABEL_LENGTH];
    const char *number_out;
//    address addr;
//    const ipv4_addr_and_mask *ipv4;
//    const ipv6_addr_and_prefix *ipv6;

    switch (hfinfo->type) {

        case FT_NONE:
        case FT_PROTOCOL:
            return protol_strlcpy(display_label_str, UTF8_CHECK_MARK, label_str_size);

        case FT_UINT_BYTES:
        case FT_BYTES:
            tmp_str = format_bytes_hfinfo_maxlen(hfinfo,
                fvalue_get_bytes_data(finfo->value),
                (unsigned)fvalue_length2(finfo->value),
                label_str_size);
            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
            wmem_free(tmp_str);
            break;

//        case FT_ABSOLUTE_TIME:
//            tmp_str = abs_time_to_str(NULL, fvalue_get_time(finfo->value), hfinfo->display, true);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

//        case FT_RELATIVE_TIME:
//            tmp_str = rel_time_to_secs_str(NULL, fvalue_get_time(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

        case FT_BOOLEAN:
            number64 = fvalue_get_uinteger64(finfo->value);
            label_len = protol_strlcpy(display_label_str,
                    tfs_get_string(!!number64, hfinfo->strings), label_str_size);
            break;

        case FT_CHAR:
            number = fvalue_get_uinteger(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_t fmtfunc = (custom_fmt_func_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc);
                fmtfunc(tmp, number);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);

            } else if (hfinfo->strings) {
                number_out = hf_try_val_to_str(number, hfinfo);

                if (!number_out) {
                    number_out = hfinfo_char_value_format_display(BASE_HEX, number_buf, number);
                }

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);

            } else {
                number_out = hfinfo_char_value_format(hfinfo, number_buf, number);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

        /* XXX - make these just FT_NUMBER? */
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
        case FT_FRAMENUM:
            hf_str_val = NULL;
            number = FT_IS_INT(hfinfo->type) ?
                (uint32_t)fvalue_get_sinteger(finfo->value) :
                fvalue_get_uinteger(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_t fmtfunc = (custom_fmt_func_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc);
                fmtfunc(tmp, number);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);

            } else if (hfinfo->strings && hfinfo->type != FT_FRAMENUM) {
                if (hfinfo->display & BASE_UNIT_STRING) {
                    number_out = hfinfo_numeric_value_format(hfinfo, number_buf, number);
                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                    hf_str_val = hf_try_val_to_str(number, hfinfo);
                    label_len += protol_strlcpy(display_label_str+label_len, hf_str_val, label_str_size-label_len);
                } else {
                    number_out = hf_try_val_to_str(number, hfinfo);

                    if (!number_out) {
                        number_out = hfinfo_number_value_format_display(hfinfo, hfinfo->display, number_buf, number);
                    }

                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                }
            } else {
                number_out = hfinfo_number_value_format(hfinfo, number_buf, number);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
            hf_str_val = NULL;
            number64 = FT_IS_INT(hfinfo->type) ?
                (uint64_t)fvalue_get_sinteger64(finfo->value) :
                fvalue_get_uinteger64(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_64_t fmtfunc64 = (custom_fmt_func_64_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc64);
                fmtfunc64(tmp, number64);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);
            } else if (hfinfo->strings) {
                if (hfinfo->display & BASE_UNIT_STRING) {
                    number_out = hfinfo_numeric_value_format64(hfinfo, number_buf, number64);
                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                    hf_str_val = hf_try_val64_to_str(number64, hfinfo);
                    label_len += protol_strlcpy(display_label_str+label_len, hf_str_val, label_str_size-label_len);
                } else {
                    number_out = hf_try_val64_to_str(number64, hfinfo);

                    if (!number_out)
                        number_out = hfinfo_number_value_format_display64(hfinfo, hfinfo->display, number_buf, number64);

                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                }
            } else {
                number_out = hfinfo_number_value_format64(hfinfo, number_buf, number64);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

//        case FT_EUI64:
//            tmp_str = eui64_to_str(NULL, fvalue_get_uinteger64(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

//        case FT_IPv4:
//            ipv4 = fvalue_get_ipv4(finfo->value);
//            //XXX: Should we ignore the mask?
//            set_address_ipv4(&addr, ipv4);
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            free_address(&addr);
//            break;

//        case FT_IPv6:
//            ipv6 = fvalue_get_ipv6(finfo->value);
//            set_address_ipv6(&addr, ipv6);
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            free_address(&addr);
//            break;

//        case FT_FCWWN:
//            set_address (&addr, AT_FCWWN, FCWWN_ADDR_LEN, fvalue_get_bytes_data(finfo->value));
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_ETHER:
//            set_address (&addr, AT_ETHER, FT_ETHER_LEN, fvalue_get_bytes_data(finfo->value));
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_GUID:
//            tmp_str = guid_to_str(NULL, fvalue_get_guid(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_REL_OID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = rel_oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_OID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_SYSTEM_ID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = print_system_id(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

        case FT_FLOAT:
        case FT_DOUBLE:
            label_len = (int)fill_display_label_float(finfo, display_label_str);
            break;

        case FT_STRING:
        case FT_STRINGZ:
        case FT_UINT_STRING:
        case FT_STRINGZPAD:
        case FT_STRINGZTRUNC:
            str = fvalue_get_string(finfo->value);
            label_len = (int)label_strcpy(display_label_str, label_str_size, 0, str, label_strcat_flags(hfinfo));
            if (label_len >= label_str_size) {
                /* Truncation occurred. Get the real length
                 * copied (not including '\0') */
                label_len = label_str_size ? label_str_size - 1 : 0;
            }
            break;

        default:
            /* First try ftype string representation */
            tmp_str = fvalue_to_string_repr(finfo->value, FTREPR_DISPLAY, hfinfo->display);
            if (!tmp_str) {
                /* Default to show as bytes */
                bytes = fvalue_get_bytes_data(finfo->value);
                tmp_str = bytes_to_str(bytes, fvalue_length2(finfo->value));
            }
            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
            wmem_free(tmp_str);
            break;
    }
    return label_len;
}

/* Set text of proto_item after having already been created. */
void proto_item_set_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL)
        return;

    if (fi->rep) {
        ITEM_LABEL_FREE(fi->rep);
        fi->rep = NULL;
    }

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);
}

/* Append to text of proto_item after having already been created. */
void proto_item_append_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    size_t      curlen;
    char       *str;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL) {
        return;
    }

    if (!proto_item_is_hidden(pi)) {
        /*
         * If we don't already have a representation,
         * generate the default representation.
         */
        if (fi->rep == NULL) {
            ITEM_LABEL_NEW(fi->rep);
            proto_item_fill_label(fi, fi->rep->representation);
        }
        if (fi->rep) {
            curlen = strlen(fi->rep->representation);
            /* curlen doesn't include the \0 byte.
             * XXX: If curlen + 4 > ITEM_LABEL_LENGTH, we can't tell if
             * the representation has already been truncated (of an up
             * to 4 byte UTF-8 character) or is just at the maximum length
             * unless we search for " [truncated]" (which may not be
             * at the start.)
             * It's safer to do nothing.
             */
            if (ITEM_LABEL_LENGTH > (curlen + 4)) {
                va_start(ap, format);
                str = strdup_vprintf(format, ap);
                va_end(ap);
//                WS_UTF_8_CHECK(str, -1);
                curlen = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, curlen, str, 0);
                if (curlen >= ITEM_LABEL_LENGTH) {
                    /* Uh oh, we don't have enough room.  Tell the user
                     * that the field is truncated.
                     */
                    LABEL_MARK_TRUNCATED_START(fi->rep->representation);
                }
            }
        }
    }
}

/* Prepend to text of proto_item after having already been created. */
void proto_item_prepend_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    size_t      pos;
    char        representation[ITEM_LABEL_LENGTH];
    char       *str;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL) {
        return;
    }

    if (!proto_item_is_hidden(pi)) {
        /*
         * If we don't already have a representation,
         * generate the default representation.
         */
        if (fi->rep == NULL) {
            ITEM_LABEL_NEW(fi->rep);
            proto_item_fill_label(fi, representation);
        } else
            (void) g_strlcpy(representation, fi->rep->representation, ITEM_LABEL_LENGTH);

        va_start(ap, format);
        str = strdup_vprintf(format, ap);
        va_end(ap);
//        WS_UTF_8_CHECK(str, -1);
        pos = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, 0, str, 0);
        pos = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, pos, representation, 0);
        /* XXX: As above, if the old representation is close to the label
         * length, it might already be marked as truncated. */
        if (pos >= ITEM_LABEL_LENGTH && (strlen(representation) + 4) <= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            LABEL_MARK_TRUNCATED_START(fi->rep->representation);
        }
    }
}

static void finfo_set_len(field_info *fi, const int length)
{
    int length_remaining;

    DISSECTOR_ASSERT_HINT(length >= 0, fi->hfinfo->abbrev);
    length_remaining = tvb_captured_length_remaining(fi->ds_tvb, fi->start);
    if (length > length_remaining)
        fi->length = length_remaining;
    else
        fi->length = length;

    /* If we have an FT_PROTOCOL we need to set the length of the fvalue tvbuff as well. */
    if (fvalue_type_ftenum(fi->value) == FT_PROTOCOL) {
        fvalue_set_protocol(fi->value, NULL, NULL, fi->length);
    }

    /*
     * You cannot just make the "len" field of a GByteArray
     * larger, if there's no data to back that length;
     * you can only make it smaller.
     */
    if (fvalue_type_ftenum(fi->value) == FT_BYTES && fi->length > 0) {
        GBytes *bytes = fvalue_get_bytes(fi->value);
        size_t size;
        const void *data = g_bytes_get_data(bytes, &size);
        if ((size_t)fi->length <= size) {
            fvalue_set_bytes_data(fi->value, data, fi->length);
        }
        g_bytes_unref(bytes);
    }
}

void proto_item_set_len(proto_item *pi, const int length)
{
    field_info *fi;

    /* If the item is not visible, we can't set the length because
     * we can't distinguish which proto item this is being called
     * on, since faked items share proto items. (#17877)
     */
    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL)
        return;

    finfo_set_len(fi, length);
}

proto_tree *proto_tree_create_root(packet_info *pinfo)
{
    proto_node *pnode;

    /* Initialize the proto_node */
    pnode = malloc(sizeof(proto_tree));
    PROTO_NODE_INIT(pnode);
    pnode->parent = NULL;
    PNODE_FINFO(pnode) = NULL;
    pnode->tree_data = malloc(sizeof(tree_data_t));

    /* Make sure we can access pinfo everywhere */
    pnode->tree_data->pinfo = pinfo;

    /* Don't initialize the tree_data_t. Wait until we know we need it */
    pnode->tree_data->interesting_hfids = NULL;

    /* Set the default to false so it's easier to
     * find errors; if we expect to see the protocol tree
     * but for some reason the default 'visible' is not
     * changed, then we'll find out very quickly. */
    pnode->tree_data->visible = false;

    /* Make sure that we fake protocols (if possible) */
    pnode->tree_data->fake_protocols = true;

    /* Keep track of the number of children */
    pnode->tree_data->count = 0;

    return (proto_tree *)pnode;
}


/* "prime" a proto_tree with a single hfid that a dfilter
 * is interested in. */
void proto_tree_prime_with_hfid(proto_tree *tree, const int hfid)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    /* this field is referenced by a filter so increase the refcount.
       also increase the refcount for the parent, i.e the protocol.
       Don't increase the refcount if we're already printing the
       type, as that is a superset of direct reference.
    */
    if (hfinfo->ref_type != HF_REF_TYPE_PRINT) {
        hfinfo->ref_type = HF_REF_TYPE_DIRECT;
    }
    /* only increase the refcount if there is a parent.
       if this is a protocol and not a field then parent will be -1
       and there is no parent to add any refcounting for.
    */
    if (hfinfo->parent != -1) {
        header_field_info *parent_hfinfo;
        PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);

        /* Mark parent as indirectly referenced unless it is already directly
         * referenced, i.e. the user has specified the parent in a filter.
         */
        if (parent_hfinfo->ref_type == HF_REF_TYPE_NONE)
            parent_hfinfo->ref_type = HF_REF_TYPE_INDIRECT;
    }
}

/* "prime" a proto_tree with a single hfid that a dfilter
 * is interested in. */
void proto_tree_prime_with_hfid_print(proto_tree *tree, const int hfid)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    /* this field is referenced by an (output) filter so increase the refcount.
       also increase the refcount for the parent, i.e the protocol.
    */
    hfinfo->ref_type = HF_REF_TYPE_PRINT;
    /* only increase the refcount if there is a parent.
       if this is a protocol and not a field then parent will be -1
       and there is no parent to add any refcounting for.
    */
    if (hfinfo->parent != -1) {
        header_field_info *parent_hfinfo;
        PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);

        /* Mark parent as indirectly referenced unless it is already directly
         * referenced, i.e. the user has specified the parent in a filter.
         */
        if (parent_hfinfo->ref_type == HF_REF_TYPE_NONE)
            parent_hfinfo->ref_type = HF_REF_TYPE_INDIRECT;
    }
}

proto_tree *proto_item_add_subtree(proto_item *pi,    const int idx)
{
    field_info *fi;

    if (!pi)
        return NULL;

    DISSECTOR_ASSERT(idx >= 0 && idx < num_tree_types);

    fi = PITEM_FINFO(pi);
    if (!fi)
        return (proto_tree *)pi;

    fi->tree_type = idx;

    return (proto_tree *)pi;
}

proto_tree *proto_item_get_subtree(proto_item *pi)
{
    field_info *fi;

    if (!pi)
        return NULL;
    fi = PITEM_FINFO(pi);
    if ( (!fi) || (fi->tree_type == -1) )
        return NULL;
    return (proto_tree *)pi;
}

static void check_protocol_filter_name_or_fail(const char *filter_name)
{
    /* Require at least two characters. */
    if (filter_name[0] == '\0' || filter_name[1] == '\0') {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" cannot have length less than two.", filter_name);
    }

    if (proto_check_field_name(filter_name) != '\0') {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" has one or more invalid characters."
            " Allowed are letters, digits, '-', '_' and non-repeating '.'."
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }

    /* Check that it doesn't match some very common numeric forms. */
    if (filter_name[0] == '0' &&
                (filter_name[1] == 'x' || filter_name[1] == 'X' ||
                filter_name[1] == 'b' || filter_name[1] == 'B')) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" cannot start with \"%c%c\".",
                        filter_name, filter_name[0], filter_name[1]);
    }

    /* Names starting with a digit must not contain a minus sign (currently not checked at runtime). */

    /* Check that it contains at least one letter. */
    bool have_letter = false;
    for (const char *s = filter_name; *s != '\0'; s++) {
        if (g_ascii_isalpha(*s)) {
            have_letter = true;
            break;
        }
    }
    if (!have_letter) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" must contain at least one letter a-z.",
                        filter_name);
    }

    /* Check for reserved keywords. */
    if (g_hash_table_contains(proto_reserved_filter_names, filter_name)) {
        REPORT_DISSECTOR_BUG("Protocol filter name \"%s\" is invalid because it is a reserved keyword."
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }
}

int proto_register_protocol(const char *name, const char *short_name,
            const char *filter_name)
{
    protocol_t *protocol;
    header_field_info *hfinfo;

    /*
     * Make sure there's not already a protocol with any of those
     * names.  Crash if there is, as that's an error in the code
     * or an inappropriate plugin.
     * This situation has to be fixed to not register more than one
     * protocol with the same name.
     */

    if (g_hash_table_lookup(proto_names, name)) {
        /* ws_error will terminate the program */
        REPORT_DISSECTOR_BUG("Duplicate protocol name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", name);
    }

    if (g_hash_table_lookup(proto_short_names, short_name)) {
        REPORT_DISSECTOR_BUG("Duplicate protocol short_name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", short_name);
    }

    check_protocol_filter_name_or_fail(filter_name);

    if (g_hash_table_lookup(proto_filter_names, filter_name)) {
        REPORT_DISSECTOR_BUG("Duplicate protocol filter_name \"%s\"!"
            " This might be caused by an inappropriate plugin or a development error.", filter_name);
    }

    /*
     * Add this protocol to the list of known protocols;
     * the list is sorted by protocol short name.
     */
    protocol = g_new(protocol_t, 1);
    protocol->name = name;
    protocol->short_name = short_name;
    protocol->filter_name = filter_name;
    protocol->fields = NULL; /* Delegate until actually needed */
    protocol->is_enabled = true; /* protocol is enabled by default */
    protocol->enabled_by_default = true; /* see previous comment */
    protocol->can_toggle = true;
    protocol->parent_proto_id = -1;

    /* List will be sorted later by name, when all protocols completed registering */
    protocols = g_list_prepend(protocols, protocol);
    g_hash_table_insert(proto_names, (void *)name, protocol);
    g_hash_table_insert(proto_filter_names, (void *)filter_name, protocol);
    g_hash_table_insert(proto_short_names, (void *)short_name, protocol);

    /* Here we allocate a new header_field_info struct */
    hfinfo = g_slice_new(header_field_info);
    hfinfo->name = name;
    hfinfo->abbrev = filter_name;
    hfinfo->type = FT_PROTOCOL;
    hfinfo->display = BASE_NONE;
    hfinfo->strings = protocol;
    hfinfo->bitmask = 0;
    hfinfo->ref_type = HF_REF_TYPE_NONE;
    hfinfo->blurb = NULL;
    hfinfo->parent = -1; /* This field differentiates protos and fields */

    protocol->proto_id = proto_register_field_init(hfinfo, hfinfo->parent);

    return protocol->proto_id;
}

bool proto_deregister_protocol(const char *short_name)
{
    protocol_t *protocol;
    header_field_info *hfinfo;
    int proto_id;
    unsigned i;

    proto_id = proto_get_id_by_short_name(short_name);
    protocol = find_protocol_by_id(proto_id);
    if (protocol == NULL)
        return false;

    g_hash_table_remove(proto_names, protocol->name);
    g_hash_table_remove(proto_short_names, (void *)short_name);
    g_hash_table_remove(proto_filter_names, (void *)protocol->filter_name);

    if (protocol->fields) {
        for (i = 0; i < protocol->fields->len; i++) {
            hfinfo = (header_field_info *)g_ptr_array_index(protocol->fields, i);
            g_ptr_array_add(deregistered_fields, gpa_hfinfo.hfi[hfinfo->id]);
        }
        g_ptr_array_free(protocol->fields, true);
        protocol->fields = NULL;
    }

    /* Remove this protocol from the list of known protocols */
    protocols = g_list_remove(protocols, protocol);

    g_ptr_array_add(deregistered_fields, gpa_hfinfo.hfi[proto_id]);
    g_hash_table_steal(gpa_name_map, protocol->filter_name);

    g_free(last_field_name);
    last_field_name = NULL;

    return true;
}

/*
 * Routines to use to iterate over the protocols.
 * The argument passed to the iterator routines is an opaque cookie to
 * their callers; it's the GList pointer for the current element in
 * the list.
 * The ID of the protocol is returned, or -1 if there is no protocol.
 */
int proto_get_first_protocol(void **cookie)
{
    protocol_t *protocol;

    if (protocols == NULL)
        return -1;
    *cookie = protocols;
    protocol = (protocol_t *)protocols->data;

    return protocol->proto_id;
}

int proto_get_data_protocol(void *cookie)
{
    GList *list_item = (GList *)cookie;

    protocol_t *protocol = (protocol_t *)list_item->data;

    return protocol->proto_id;
}

int proto_get_next_protocol(void **cookie)
{
    GList      *list_item = (GList *)*cookie;
    protocol_t *protocol;

    list_item = g_list_next(list_item);
    if (list_item == NULL)
        return -1;

    *cookie = list_item;
    protocol = (protocol_t *)list_item->data;

    return protocol->proto_id;
}

protocol_t *find_protocol_by_id(const int proto_id)
{
    header_field_info *hfinfo;

    if (proto_id <= 0)
        return NULL;

    PROTO_REGISTRAR_GET_NTH(proto_id, hfinfo);
    if (hfinfo->type != FT_PROTOCOL) {
        DISSECTOR_ASSERT(hfinfo->display & BASE_PROTOCOL_INFO);
    }
    return (protocol_t *)hfinfo->strings;
}

int proto_get_id(const protocol_t *protocol)
{
    return protocol->proto_id;
}

bool proto_name_already_registered(const char *name)
{
    DISSECTOR_ASSERT_HINT(name, "No name present");

    if (g_hash_table_lookup(proto_names, name) != NULL)
        return true;
    return false;
}

int proto_get_id_by_filter_name(const char *filter_name)
{
    const protocol_t *protocol = NULL;

    DISSECTOR_ASSERT_HINT(filter_name, "No filter name present");

    protocol = (const protocol_t *)g_hash_table_lookup(proto_filter_names, filter_name);

    if (protocol == NULL)
        return -1;

    return protocol->proto_id;
}

int proto_get_id_by_short_name(const char *short_name)
{
    const protocol_t *protocol = NULL;

    DISSECTOR_ASSERT_HINT(short_name, "No short name present");

    protocol = (const protocol_t *)g_hash_table_lookup(proto_short_names, short_name);

    if (protocol == NULL)
        return -1;

    return protocol->proto_id;
}

const char *proto_get_protocol_name(const int proto_id)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);

    if (protocol == NULL)
        return NULL;

    return protocol->name;
}

const char *proto_get_protocol_short_name(const protocol_t *protocol)
{
    if (protocol == NULL)
        return "(none)";

    return protocol->short_name;
}

const char *proto_get_protocol_long_name(const protocol_t *protocol)
{
    if (protocol == NULL)
        return "(none)";

    return protocol->name;
}

const char *proto_get_protocol_filter_name(const int proto_id)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);
    if (protocol == NULL)
        return "(none)";

    return protocol->filter_name;
}

bool proto_is_pino(const protocol_t *protocol)
{
    return (protocol->parent_proto_id != -1);
}

bool proto_is_protocol_enabled(const protocol_t *protocol)
{
    if (protocol == NULL)
        return false;

    //parent protocol determines enable/disable for helper dissectors
    if (proto_is_pino(protocol))
        return proto_is_protocol_enabled(find_protocol_by_id(protocol->parent_proto_id));

    return protocol->is_enabled;
}

void proto_set_decoding(const int proto_id, const bool enabled)
{
    protocol_t *protocol;

    protocol = find_protocol_by_id(proto_id);
    DISSECTOR_ASSERT(proto_is_pino(protocol) == false);
    protocol->is_enabled = enabled;
}

void proto_disable_all(void)
{
    /* This doesn't explicitly disable heuristic protocols,
     * but the heuristic doesn't get called if the parent
     * protocol isn't enabled.
     */
    protocol_t *protocol;
    GList      *list_item = protocols;

    if (protocols == NULL)
        return;

    while (list_item) {
        protocol = (protocol_t *)list_item->data;
        if (protocol->can_toggle) {
            protocol->is_enabled = false;
        }
        list_item = g_list_next(list_item);
    }
}

void proto_reenable_all(void)
{
    protocol_t *protocol;
    GList      *list_item = protocols;

    if (protocols == NULL)
        return;

    while (list_item) {
        protocol = (protocol_t *)list_item->data;
        if (protocol->can_toggle) {
            protocol->is_enabled = protocol->enabled_by_default;
        }
        list_item = g_list_next(list_item);
    }
}

static int proto_register_field_common(protocol_t *proto, header_field_info *hfi, const int parent)
{
    if (proto != NULL) {
        g_ptr_array_add(proto->fields, hfi);
    }

    return proto_register_field_init(hfi, parent);
}

/* for use with static arrays only, since we don't allocate our own copies
of the header_field_info struct contained within the hf_register_info struct */
void proto_register_field_array(const int parent, hf_register_info *hf, const int num_records)
{
    hf_register_info *ptr = hf;
    protocol_t     *proto;
    int          i;

    proto = find_protocol_by_id(parent);

    if (proto->fields == NULL) {
        proto->fields = g_ptr_array_sized_new(num_records);
    }

    for (i = 0; i < num_records; i++, ptr++) {
        /*
         * Make sure we haven't registered this yet.
         * Most fields have variables associated with them
         * that are initialized to -1; some have array elements,
         * or possibly uninitialized variables, so we also allow
         * 0 (which is unlikely to be the field ID we get back
         * from "proto_register_field_init()").
         */
        if (*ptr->p_id != -1 && *ptr->p_id != 0) {
            REPORT_DISSECTOR_BUG(
                "Duplicate field detected in call to proto_register_field_array: %s is already registered",
                ptr->hfinfo.abbrev);
            return;
        }

        *ptr->p_id = proto_register_field_common(proto, &ptr->hfinfo, parent);
    }
}

void proto_add_deregistered_data (void *data)
{
    g_ptr_array_add(deregistered_data, data);
}

void proto_free_field_strings (ftenum_t field_type, unsigned int field_display, const void *field_strings)
{
    if (field_strings == NULL) {
        return;
    }

    switch (field_type) {
        case FT_FRAMENUM:
            /* This is just an integer represented as a pointer */
            break;
        case FT_PROTOCOL: {
            protocol_t *protocol = (protocol_t *)field_strings;
            g_free((char *)protocol->short_name);
            break;
        }
        case FT_BOOLEAN: {
            true_false_string *tf = (true_false_string *)field_strings;
            g_free((char *)tf->true_string);
            g_free((char *)tf->false_string);
            break;
        }
        case FT_UINT40:
        case FT_INT40:
        case FT_UINT48:
        case FT_INT48:
        case FT_UINT56:
        case FT_INT56:
        case FT_UINT64:
        case FT_INT64: {
            if (field_display & BASE_UNIT_STRING) {
                unit_name_string *unit = (unit_name_string *)field_strings;
                g_free((char *)unit->singular);
                g_free((char *)unit->plural);
            } else if (field_display & BASE_RANGE_STRING) {
                range_string *rs = (range_string *)field_strings;
                while (rs->strptr) {
                    g_free((char *)rs->strptr);
                    rs++;
                }
            } else if (field_display & BASE_EXT_STRING) {
                val64_string_ext *vse = (val64_string_ext *)field_strings;
                val64_string *vs = (val64_string *)vse->_vs_p;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
                val64_string_ext_free(vse);
                field_strings = NULL;
            } else if (field_display == BASE_CUSTOM) {
                /* this will be a pointer to a function, don't free that */
                field_strings = NULL;
            } else {
                val64_string *vs64 = (val64_string *)field_strings;
                while (vs64->strptr) {
                    g_free((char *)vs64->strptr);
                    vs64++;
                }
            }
            break;
        }
        case FT_CHAR:
        case FT_UINT8:
        case FT_INT8:
        case FT_UINT16:
        case FT_INT16:
        case FT_UINT24:
        case FT_INT24:
        case FT_UINT32:
        case FT_INT32:
        case FT_FLOAT:
        case FT_DOUBLE: {
            if (field_display & BASE_UNIT_STRING) {
                unit_name_string *unit = (unit_name_string *)field_strings;
                g_free((char *)unit->singular);
                g_free((char *)unit->plural);
            } else if (field_display & BASE_RANGE_STRING) {
                range_string *rs = (range_string *)field_strings;
                while (rs->strptr) {
                    g_free((char *)rs->strptr);
                    rs++;
                }
            } else if (field_display & BASE_EXT_STRING) {
                value_string_ext *vse = (value_string_ext *)field_strings;
                value_string *vs = (value_string *)vse->_vs_p;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
                value_string_ext_free(vse);
                field_strings = NULL;
            } else if (field_display == BASE_CUSTOM) {
                /* this will be a pointer to a function, don't free that */
                field_strings = NULL;
            } else {
                value_string *vs = (value_string *)field_strings;
                while (vs->strptr) {
                    g_free((char *)vs->strptr);
                    vs++;
                }
            }
            break;
        default:
            break;
        }
    }

    if (field_type != FT_FRAMENUM) {
        g_free((void *)field_strings);
    }
}

static void free_deregistered_field (void *data, void *user_data)
{
    header_field_info *hfi = (header_field_info *) data;
    int hf_id = hfi->id;

    g_free((char *)hfi->name);
    g_free((char *)hfi->abbrev);
    g_free((char *)hfi->blurb);

    proto_free_field_strings(hfi->type, hfi->display, hfi->strings);

    if (hfi->parent == -1)
        g_slice_free(header_field_info, hfi);

    gpa_hfinfo.hfi[hf_id] = NULL; /* Invalidate this hf_id / proto_id */
}

static void free_deregistered_data (void *data, void *user_data)
{
    g_free (data);
}

static void free_deregistered_slice (void *data, void *user_data)
{
    struct g_slice_data *slice_data = (struct g_slice_data *)data;

    g_slice_free1(slice_data->block_size, slice_data->mem_block);
    g_slice_free(struct g_slice_data, slice_data);
}

/* free deregistered fields and data */
void proto_free_deregistered_fields (void)
{
//    expert_free_deregistered_expertinfos();

    g_ptr_array_foreach(deregistered_fields, free_deregistered_field, NULL);
    g_ptr_array_free(deregistered_fields, true);
    deregistered_fields = g_ptr_array_new();

    g_ptr_array_foreach(deregistered_data, free_deregistered_data, NULL);
    g_ptr_array_free(deregistered_data, true);
    deregistered_data = g_ptr_array_new();

    g_ptr_array_foreach(deregistered_slice, free_deregistered_slice, NULL);
    g_ptr_array_free(deregistered_slice, true);
    deregistered_slice = g_ptr_array_new();
}

static const value_string hf_display[] = {
    { BASE_NONE,              "BASE_NONE"               },
    { BASE_DEC,              "BASE_DEC"               },
    { BASE_HEX,              "BASE_HEX"               },
    { BASE_OCT,              "BASE_OCT"               },
    { BASE_DEC_HEX,              "BASE_DEC_HEX"           },
    { BASE_HEX_DEC,              "BASE_HEX_DEC"           },
    { BASE_CUSTOM,              "BASE_CUSTOM"               },
    { BASE_NONE|BASE_RANGE_STRING,    "BASE_NONE|BASE_RANGE_STRING"       },
    { BASE_DEC|BASE_RANGE_STRING,     "BASE_DEC|BASE_RANGE_STRING"       },
    { BASE_HEX|BASE_RANGE_STRING,     "BASE_HEX|BASE_RANGE_STRING"       },
    { BASE_OCT|BASE_RANGE_STRING,     "BASE_OCT|BASE_RANGE_STRING"       },
    { BASE_DEC_HEX|BASE_RANGE_STRING, "BASE_DEC_HEX|BASE_RANGE_STRING" },
    { BASE_HEX_DEC|BASE_RANGE_STRING, "BASE_HEX_DEC|BASE_RANGE_STRING" },
    { BASE_CUSTOM|BASE_RANGE_STRING,  "BASE_CUSTOM|BASE_RANGE_STRING"  },
    { BASE_NONE|BASE_VAL64_STRING,    "BASE_NONE|BASE_VAL64_STRING"       },
    { BASE_DEC|BASE_VAL64_STRING,     "BASE_DEC|BASE_VAL64_STRING"       },
    { BASE_HEX|BASE_VAL64_STRING,     "BASE_HEX|BASE_VAL64_STRING"       },
    { BASE_OCT|BASE_VAL64_STRING,     "BASE_OCT|BASE_VAL64_STRING"       },
    { BASE_DEC_HEX|BASE_VAL64_STRING, "BASE_DEC_HEX|BASE_VAL64_STRING" },
    { BASE_HEX_DEC|BASE_VAL64_STRING, "BASE_HEX_DEC|BASE_VAL64_STRING" },
    { BASE_CUSTOM|BASE_VAL64_STRING,  "BASE_CUSTOM|BASE_VAL64_STRING"  },
    { ABSOLUTE_TIME_LOCAL,          "ABSOLUTE_TIME_LOCAL"           },
    { ABSOLUTE_TIME_UTC,          "ABSOLUTE_TIME_UTC"           },
    { ABSOLUTE_TIME_DOY_UTC,      "ABSOLUTE_TIME_DOY_UTC"       },
    { BASE_PT_UDP,              "BASE_PT_UDP"               },
    { BASE_PT_TCP,              "BASE_PT_TCP"               },
    { BASE_PT_DCCP,              "BASE_PT_DCCP"           },
    { BASE_PT_SCTP,              "BASE_PT_SCTP"           },
    { BASE_OUI,              "BASE_OUI"               },
    { 0,                  NULL } };

const char* proto_field_display_to_string(int field_display)
{
    return val_to_str_const(field_display, hf_display, "Unknown");
}

/* Types of port numbers Wireshark knows about. */
typedef enum {
    PT_NONE,            /* no port number */
    PT_SCTP,            /* SCTP */
    PT_TCP,             /* TCP */
    PT_UDP,             /* UDP */
    PT_DCCP,            /* DCCP */
    PT_IPX,             /* IPX sockets */
    PT_DDP,             /* DDP AppleTalk connection */
    PT_IDP,             /* XNS IDP sockets */
    PT_USB,             /* USB endpoint 0xffff means the host */
    PT_I2C,
    PT_IBQP,            /* Infiniband QP number */
    PT_BLUETOOTH,
    PT_IWARP_MPA,       /* iWarp MPA */
    PT_MCTP
} port_type;

static inline port_type display_to_port_type(field_display_e e)
{
    switch (e) {
    case BASE_PT_UDP:
        return PT_UDP;
    case BASE_PT_TCP:
        return PT_TCP;
    case BASE_PT_DCCP:
        return PT_DCCP;
    case BASE_PT_SCTP:
        return PT_SCTP;
    default:
        break;
    }
    return PT_NONE;
}

#define PRE_ALLOC_EXPERT_FIELDS_MEM 5000
#define PROTO_PRE_ALLOC_HF_FIELDS_MEM (300000+PRE_ALLOC_EXPERT_FIELDS_MEM)
static int proto_register_field_init(header_field_info *hfinfo, const int parent)
{
    hfinfo->parent         = parent;
    hfinfo->same_name_next = NULL;
    hfinfo->same_name_prev_id = -1;

    /* if we always add and never delete, then id == len - 1 is correct */
    if (gpa_hfinfo.len >= gpa_hfinfo.allocated_len) {
        if (!gpa_hfinfo.hfi) {
            gpa_hfinfo.allocated_len = PROTO_PRE_ALLOC_HF_FIELDS_MEM;
            gpa_hfinfo.hfi = (header_field_info **)g_malloc(sizeof(header_field_info *)*PROTO_PRE_ALLOC_HF_FIELDS_MEM);
            /* The entry with index 0 is not used. */
            gpa_hfinfo.hfi[0] = NULL;
            gpa_hfinfo.len = 1;
        } else {
            gpa_hfinfo.allocated_len += 1000;
            gpa_hfinfo.hfi = (header_field_info **)g_realloc(gpa_hfinfo.hfi,
                           sizeof(header_field_info *)*gpa_hfinfo.allocated_len);
            /*printf("gpa_hfinfo.allocated_len %u", gpa_hfinfo.allocated_len);*/
        }
    }
    gpa_hfinfo.hfi[gpa_hfinfo.len] = hfinfo;
    gpa_hfinfo.len++;
    hfinfo->id = gpa_hfinfo.len - 1;

    /* if we have real names, enter this field in the name tree */
    if ((hfinfo->name[0] != 0) && (hfinfo->abbrev[0] != 0 )) {

        header_field_info *same_name_next_hfinfo;
        unsigned char c;

        /* Check that the filter name (abbreviation) is legal;
         * it must contain only alphanumerics, '-', "_", and ".". */
        c = proto_check_field_name(hfinfo->abbrev);
        if (c) {
            if (c == '.') {
                REPORT_DISSECTOR_BUG("Invalid leading, duplicated or trailing '.' found in filter name '%s'", hfinfo->abbrev);
            } else if (g_ascii_isprint(c)) {
                REPORT_DISSECTOR_BUG("Invalid character '%c' in filter name '%s'", c, hfinfo->abbrev);
            } else {
                REPORT_DISSECTOR_BUG("Invalid byte \\%03o in filter name '%s'", c, hfinfo->abbrev);
            }
        }

        /* We allow multiple hfinfo's to be registered under the same
         * abbreviation. This was done for X.25, as, depending
         * on whether it's modulo-8 or modulo-128 operation,
         * some bitfield fields may be in different bits of
         * a byte, and we want to be able to refer to that field
         * with one name regardless of whether the packets
         * are modulo-8 or modulo-128 packets. */

        same_name_hfinfo = NULL;

        g_hash_table_insert(gpa_name_map, (void *) (hfinfo->abbrev), hfinfo);
        /* GLIB 2.x - if it is already present
         * the previous hfinfo with the same name is saved
         * to same_name_hfinfo by value destroy callback */
        if (same_name_hfinfo) {
            /* There's already a field with this name.
             * Put the current field *before* that field
             * in the list of fields with this name, Thus,
             * we end up with an effectively
             * doubly-linked-list of same-named hfinfo's,
             * with the head of the list (stored in the
             * hash) being the last seen hfinfo.
             */
            same_name_next_hfinfo =
                same_name_hfinfo->same_name_next;

            hfinfo->same_name_next = same_name_next_hfinfo;
            if (same_name_next_hfinfo)
                same_name_next_hfinfo->same_name_prev_id = hfinfo->id;

            same_name_hfinfo->same_name_next = hfinfo;
            hfinfo->same_name_prev_id = same_name_hfinfo->id;
#ifdef ENABLE_CHECK_FILTER
            while (same_name_hfinfo) {
                if (_ftype_common(hfinfo->type) != _ftype_common(same_name_hfinfo->type))
                    printf("'%s' exists multiple times with incompatible types: %s and %s", hfinfo->abbrev, ftype_name(hfinfo->type), ftype_name(same_name_hfinfo->type));
                same_name_hfinfo = same_name_hfinfo->same_name_next;
            }
#endif
        }
    }

    return hfinfo->id;
}

void proto_register_subtree_array(int * const *indices, const int num_indices)
{
    int    i;
    int    *const *ptr = indices;

    /*
     * If we've already allocated the array of tree types, expand
     * it; this lets plugins such as mate add tree types after
     * the initial startup.  (If we haven't already allocated it,
     * we don't allocate it; on the first pass, we just assign
     * ett values and keep track of how many we've assigned, and
     * when we're finished registering all dissectors we allocate
     * the array, so that we do only one allocation rather than
     * wasting CPU time and memory by growing the array for each
     * dissector that registers ett values.)
     */
    if (tree_is_expanded != NULL) {
        tree_is_expanded = (uint32_t *)g_realloc(tree_is_expanded, (1+((num_tree_types + num_indices)/32)) * sizeof(uint32_t));

        /* set new items to 0 */
        /* XXX, slow!!! optimize when needed (align 'i' to 32, and set rest of uint32_t to 0) */
        for (i = num_tree_types; i < num_tree_types + num_indices; i++)
            tree_is_expanded[i >> 5] &= ~(1U << (i & 31));
    }

    /*
     * Assign "num_indices" subtree numbers starting at "num_tree_types",
     * returning the indices through the pointers in the array whose
     * first element is pointed to by "indices", and update
     * "num_tree_types" appropriately.
     */
    for (i = 0; i < num_indices; i++, ptr++, num_tree_types++) {
        if (**ptr != -1 && **ptr != 0) {
            REPORT_DISSECTOR_BUG("register_subtree_array: subtree item type (ett_...) not -1 or 0 !"
                " This is a development error:"
                " Either the subtree item type has already been assigned or"
                " was not initialized to -1 or 0.");
        }
        **ptr = num_tree_types;
    }
}

static void mark_truncated(char *label_str, size_t name_pos, const size_t size)
{
    static const char  trunc_str[] = " [" UTF8_HORIZONTAL_ELLIPSIS "]";
    const size_t       trunc_len = sizeof(trunc_str)-1;
    char              *last_char;

    /* ..... field_name: dataaaaaaaaaaaaa
     *                 |
     *                 ^^^^^ name_pos
     *
     * ..... field_name […]: dataaaaaaaaaaaaa
     *
     * name_pos==0 means that we have only data or only a field_name
     */

    if (name_pos < size - trunc_len) {
        memmove(label_str + name_pos + trunc_len, label_str + name_pos, size - name_pos - trunc_len);
        memcpy(label_str + name_pos, trunc_str, trunc_len);

        /* in general, label_str is UTF-8
           we can truncate it only at the beginning of a new character
           we go backwards from the byte right after our buffer and
            find the next starting byte of a UTF-8 character, this is
            where we cut
           there's no need to use g_utf8_find_prev_char(), the search
            will always succeed since we copied trunc_str into the
            buffer */
        /* g_utf8_prev_char does not deference the memory address
         * passed in (until after decrementing it, so it is perfectly
         * legal to pass in a pointer one past the last element.
         */
        last_char = g_utf8_prev_char(label_str + size);
        *last_char = '\0';

    } else if (name_pos < size)
        (void) g_strlcpy(label_str + name_pos, trunc_str, size - name_pos);
}

static void label_mark_truncated(char *label_str, size_t name_pos)
{
    mark_truncated(label_str, name_pos, ITEM_LABEL_LENGTH);
}

static size_t label_fill(char *label_str, size_t pos, const header_field_info *hfinfo, const char *text)
{
    size_t name_pos;

    /* "%s: %s", hfinfo->name, text */
    name_pos = pos = label_concat(label_str, pos, hfinfo->name);
    if (!(hfinfo->display & BASE_NO_DISPLAY_VALUE)) {
        pos = label_concat(label_str, pos, ": ");
        pos = label_strcpy(label_str, ITEM_LABEL_LENGTH, pos, text ? text : "(null)", label_strcat_flags(hfinfo));
    }

    if (pos >= ITEM_LABEL_LENGTH) {
        /* Uh oh, we don't have enough room. Tell the user that the field is truncated. */
        label_mark_truncated(label_str, name_pos);
    }

    return pos;
}

static size_t label_fill_descr(char *label_str, size_t pos, const header_field_info *hfinfo, const char *text, const char *descr)
{
    size_t name_pos;

    /* "%s: %s (%s)", hfinfo->name, text, descr */
    name_pos = pos = label_concat(label_str, pos, hfinfo->name);
    if (!(hfinfo->display & BASE_NO_DISPLAY_VALUE)) {
        pos = label_concat(label_str, pos, ": ");
        if (hfinfo->display & BASE_UNIT_STRING) {
            pos = label_concat(label_str, pos, descr ? descr : "(null)");
            pos = label_concat(label_str, pos, text ? text : "(null)");
        } else {
            pos = label_concat(label_str, pos, text ? text : "(null)");
            pos = label_concat(label_str, pos, " (");
            pos = label_concat(label_str, pos, descr ? descr : "(null)");
            pos = label_concat(label_str, pos, ")");
        }
    }

    if (pos >= ITEM_LABEL_LENGTH) {
        /* Uh oh, we don't have enough room. Tell the user that the field is truncated. */
        label_mark_truncated(label_str, name_pos);
    }

    return pos;
}

void proto_item_fill_label(const field_info *fi, char *label_str)
{
    const header_field_info  *hfinfo;
    const char       *str;
    const uint8_t       *bytes;
    uint32_t            integer;
    uint64_t            integer64;
//    const ipv4_addr_and_mask *ipv4;
//    const ipv6_addr_and_prefix *ipv6;
//    const e_guid_t       *guid;
    char           *name;
//    address            addr;
    char           *addr_str;
    char           *tmp;

    if (!label_str) {
        printf("NULL label_str passed to proto_item_fill_label.");
        return;
    }

    label_str[0]= '\0';

    if (!fi) {
        return;
    }

    hfinfo = fi->hfinfo;

    switch (hfinfo->type) {
        case FT_NONE:
        case FT_PROTOCOL:
            (void) g_strlcpy(label_str, hfinfo->name, ITEM_LABEL_LENGTH);
            break;

        case FT_BOOLEAN:
            fill_label_boolean(fi, label_str);
            break;

        case FT_BYTES:
        case FT_UINT_BYTES:
            tmp = format_bytes_hfinfo(hfinfo,
                fvalue_get_bytes_data(fi->value),
                (unsigned)fvalue_length2(fi->value));
            label_fill(label_str, 0, hfinfo, tmp);
            free(tmp);
            break;

        case FT_CHAR:
            if (hfinfo->bitmask) {
                fill_label_bitfield_char(fi, label_str);
            } else {
                fill_label_char(fi, label_str);
            }
            break;

        /* Four types of integers to take care of:
         *    Bitfield, with val_string
         *    Bitfield, w/o val_string
         *    Non-bitfield, with val_string
         *    Non-bitfield, w/o val_string
         */
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
            if (hfinfo->bitmask) {
                fill_label_bitfield(fi, label_str, false);
            } else {
                fill_label_number(fi, label_str, false);
            }
            break;

        case FT_FRAMENUM:
            fill_label_number(fi, label_str, false);
            break;

        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
            if (hfinfo->bitmask) {
                fill_label_bitfield64(fi, label_str, false);
            } else {
                fill_label_number64(fi, label_str, false);
            }
            break;

        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
            if (hfinfo->bitmask) {
                fill_label_bitfield(fi, label_str, true);
            } else {
                fill_label_number(fi, label_str, true);
            }
            break;

        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
            if (hfinfo->bitmask) {
                fill_label_bitfield64(fi, label_str, true);
            } else {
                fill_label_number64(fi, label_str, true);
            }
            break;

        case FT_FLOAT:
        case FT_DOUBLE:
            fill_label_float(fi, label_str);
            break;

//        case FT_ABSOLUTE_TIME:
//            tmp = abs_time_to_str(NULL, fvalue_get_time(fi->value), hfinfo->display, true);
//            label_fill(label_str, 0, hfinfo, tmp);
//            wmem_free(NULL, tmp);
//            break;

//        case FT_RELATIVE_TIME:
//            tmp = rel_time_to_secs_str(NULL, fvalue_get_time(fi->value));
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s seconds", hfinfo->name, tmp);
//            wmem_free(NULL, tmp);
//            break;

//        case FT_IPXNET:
//            integer = fvalue_get_uinteger(fi->value);
//            tmp = get_ipxnet_name(NULL, integer);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s (0x%08X)", hfinfo->name,
//                   tmp, integer);
//            wmem_free(NULL, tmp);
//            break;

//        case FT_VINES:
//            addr.type = AT_VINES;
//            addr.len  = VINES_ADDR_LEN;
//            addr.data = fvalue_get_bytes_data(fi->value);

//            addr_str = (char*)address_to_str(NULL, &addr);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s", hfinfo->name, addr_str);
//            wmem_free(NULL, addr_str);
//            break;

//        case FT_ETHER:
//            bytes = fvalue_get_bytes_data(fi->value);

//            addr.type = AT_ETHER;
//            addr.len  = 6;
//            addr.data = bytes;

//            addr_str = (char*)address_with_resolution_to_str(NULL, &addr);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s", hfinfo->name, addr_str);
//            wmem_free(NULL, addr_str);
//            break;

//        case FT_IPv4:
//            ipv4 = fvalue_get_ipv4(fi->value);
//            set_address_ipv4(&addr, ipv4);

//            if (hfinfo->display == BASE_NETMASK) {
//                addr_str = (char*)address_to_str(NULL, &addr);
//            } else {
//                addr_str = (char*)address_with_resolution_to_str(NULL, &addr);
//            }
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s", hfinfo->name, addr_str);
//            wmem_free(NULL, addr_str);
//            free_address(&addr);
//            break;

//        case FT_IPv6:
//            ipv6 = fvalue_get_ipv6(fi->value);
//            set_address_ipv6(&addr, ipv6);

//            addr_str = (char*)address_with_resolution_to_str(NULL, &addr);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s", hfinfo->name, addr_str);
//            wmem_free(NULL, addr_str);
//            free_address(&addr);
//            break;

//        case FT_FCWWN:
//            bytes = fvalue_get_bytes_data(fi->value);
//            addr.type = AT_FCWWN;
//            addr.len  = FCWWN_ADDR_LEN;
//            addr.data = bytes;

//            addr_str = (char*)address_with_resolution_to_str(NULL, &addr);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                   "%s: %s", hfinfo->name, addr_str);
//            wmem_free(NULL, addr_str);
//            break;

//        case FT_GUID:
//            guid = fvalue_get_guid(fi->value);
//            tmp = guid_to_str(NULL, guid);
//            label_fill(label_str, 0, hfinfo, tmp);
//            wmem_free(NULL, tmp);
//            break;

//        case FT_OID:
//            bytes = fvalue_get_bytes_data(fi->value);
//            name = oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(fi->value));
//            tmp = oid_encoded2string(NULL, bytes, (unsigned)fvalue_length2(fi->value));
//            if (name) {
//                label_fill_descr(label_str, 0, hfinfo, tmp, name);
//                wmem_free(NULL, name);
//            } else {
//                label_fill(label_str, 0, hfinfo, tmp);
//            }
//            wmem_free(NULL, tmp);
//            break;

//        case FT_REL_OID:
//            bytes = fvalue_get_bytes_data(fi->value);
//            name = rel_oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(fi->value));
//            tmp = rel_oid_encoded2string(NULL, bytes, (unsigned)fvalue_length2(fi->value));
//            if (name) {
//                label_fill_descr(label_str, 0, hfinfo, tmp, name);
//                wmem_free(NULL, name);
//            } else {
//                label_fill(label_str, 0, hfinfo, tmp);
//            }
//            wmem_free(NULL, tmp);
//            break;

//        case FT_SYSTEM_ID:
//            bytes = fvalue_get_bytes_data(fi->value);
//            tmp = print_system_id(NULL, bytes, (int)fvalue_length2(fi->value));
//            label_fill(label_str, 0, hfinfo, tmp);
//            wmem_free(NULL, tmp);
//            break;

//        case FT_EUI64:
//            integer64 = fvalue_get_uinteger64(fi->value);
//            addr_str = eui64_to_str(NULL, integer64);
//            tmp = (char*)eui64_to_display(NULL, integer64);
//            label_fill_descr(label_str, 0, hfinfo, tmp, addr_str);
//            wmem_free(NULL, tmp);
//            wmem_free(NULL, addr_str);
//            break;
        case FT_STRING:
        case FT_STRINGZ:
        case FT_UINT_STRING:
        case FT_STRINGZPAD:
        case FT_STRINGZTRUNC:
        case FT_AX25:
            str = fvalue_get_string(fi->value);
            label_fill(label_str, 0, hfinfo, str);
            break;

//        case FT_IEEE_11073_SFLOAT:
//        case FT_IEEE_11073_FLOAT:
//            tmp = fvalue_to_string_repr(NULL, fi->value, FTREPR_DISPLAY, hfinfo->display);
//            snprintf(label_str, ITEM_LABEL_LENGTH,
//                        "%s: %s",
//                        hfinfo->name, tmp);
////            wmem_free(NULL, tmp);
//            break;

        default:
            REPORT_DISSECTOR_BUG("field %s has type %d (%s) not handled in proto_item_fill_label()",
                         hfinfo->abbrev,
                         hfinfo->type,
                         ftype_name(hfinfo->type));
            break;
    }
}

static void fill_label_boolean(const field_info *fi, char *label_str)
{
    char    *p;
    int      bitfield_byte_length = 0, bitwidth;
    uint64_t unshifted_value;
    uint64_t value;

    const header_field_info    *hfinfo   = fi->hfinfo;

    value = fvalue_get_uinteger64(fi->value);
    if (hfinfo->bitmask) {
        /* Figure out the bit width */
        bitwidth = hfinfo_container_bitwidth(hfinfo);

        /* Un-shift bits */
        unshifted_value = value;
        unshifted_value <<= hfinfo_bitshift(hfinfo);

        /* Create the bitfield first */
        p = decode_bitfield_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
        bitfield_byte_length = (int) (p - label_str);
    }

    /* Fill in the textual info */
    label_fill(label_str, bitfield_byte_length, hfinfo, tfs_get_string(!!value, hfinfo->strings));
}

static const char *hf_try_val_to_str(uint32_t value, const header_field_info *hfinfo)
{
    if (hfinfo->display & BASE_RANGE_STRING)
        return try_rval_to_str(value, (const range_string *) hfinfo->strings);

    if (hfinfo->display & BASE_EXT_STRING) {
        if (hfinfo->display & BASE_VAL64_STRING)
            return try_val64_to_str_ext(value, (val64_string_ext *) hfinfo->strings);
        else
            return try_val_to_str_ext(value, (value_string_ext *) hfinfo->strings);
    }

    if (hfinfo->display & BASE_VAL64_STRING)
        return try_val64_to_str(value, (const val64_string *) hfinfo->strings);

    if (hfinfo->display & BASE_UNIT_STRING)
        return unit_name_string_get_value(value, (const struct unit_name_string*) hfinfo->strings);

    return try_val_to_str(value, (const value_string *) hfinfo->strings);
}

static const char *hf_try_val64_to_str(uint64_t value, const header_field_info *hfinfo)
{
    if (hfinfo->display & BASE_VAL64_STRING) {
        if (hfinfo->display & BASE_EXT_STRING)
            return try_val64_to_str_ext(value, (val64_string_ext *) hfinfo->strings);
        else
            return try_val64_to_str(value, (const val64_string *) hfinfo->strings);
    }

    if (hfinfo->display & BASE_RANGE_STRING)
        return try_rval64_to_str(value, (const range_string *) hfinfo->strings);

    if (hfinfo->display & BASE_UNIT_STRING)
        return unit_name_string_get_value64(value, (const struct unit_name_string*) hfinfo->strings);

    /* If this is reached somebody registered a 64-bit field with a 32-bit
     * value-string, which isn't right. */
    REPORT_DISSECTOR_BUG("field %s is a 64-bit field with a 32-bit value_string",
        hfinfo->abbrev);

    /* This is necessary to squelch MSVC errors; is there
       any way to tell it that DISSECTOR_ASSERT_NOT_REACHED()
       never returns? */
    return NULL;
}

static const char *hf_try_double_val_to_str(double value, const header_field_info *hfinfo)
{
    if (hfinfo->display & BASE_UNIT_STRING)
        return unit_name_string_get_double(value, (const struct unit_name_string*)hfinfo->strings);

    REPORT_DISSECTOR_BUG("field %s (FT_DOUBLE) has no base_unit_string", hfinfo->abbrev);

    /* This is necessary to squelch MSVC errors; is there
       any way to tell it that DISSECTOR_ASSERT_NOT_REACHED()
       never returns? */
    return NULL;
}

static const char *hf_try_val_to_str_const(uint32_t value, const header_field_info *hfinfo, const char *unknown_str)
{
    const char *str = hf_try_val_to_str(value, hfinfo);

    return (str) ? str : unknown_str;
}

static const char *hf_try_val64_to_str_const(uint64_t value, const header_field_info *hfinfo, const char *unknown_str)
{
    const char *str = hf_try_val64_to_str(value, hfinfo);

    return (str) ? str : unknown_str;
}

/* Fills data for bitfield chars with val_strings */
static void fill_label_bitfield_char(const field_info *fi, char *label_str)
{
    char       *p;
    int         bitfield_byte_length, bitwidth;
    uint32_t    unshifted_value;
    uint32_t    value;

    char        buf[32];
    const char *out;

    const header_field_info *hfinfo = fi->hfinfo;

    /* Figure out the bit width */
    bitwidth = hfinfo_container_bitwidth(hfinfo);

    /* Un-shift bits */
    value = fvalue_get_uinteger(fi->value);

    unshifted_value = value;
    if (hfinfo->bitmask) {
        unshifted_value <<= hfinfo_bitshift(hfinfo);
    }

    /* Create the bitfield first */
    p = decode_bitfield_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
    bitfield_byte_length = (int) (p - label_str);

    /* Fill in the textual info using stored (shifted) value */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_t fmtfunc = (const custom_fmt_func_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc);
        fmtfunc(tmp, value);
        label_fill(label_str, bitfield_byte_length, hfinfo, tmp);
    }
    else if (hfinfo->strings) {
        const char *val_str = hf_try_val_to_str_const(value, hfinfo, "Unknown");

        out = hfinfo_char_vals_format(hfinfo, buf, value);
        if (out == NULL) /* BASE_NONE so don't put integer in descr */
            label_fill(label_str, bitfield_byte_length, hfinfo, val_str);
        else
            label_fill_descr(label_str, bitfield_byte_length, hfinfo, val_str, out);
    }
    else {
        out = hfinfo_char_value_format(hfinfo, buf, value);

        label_fill(label_str, bitfield_byte_length, hfinfo, out);
    }
}

/* Fills data for bitfield ints with val_strings */
static void fill_label_bitfield(const field_info *fi, char *label_str, bool is_signed)
{
    char       *p;
    int         bitfield_byte_length, bitwidth;
    uint32_t    value, unshifted_value;
    char        buf[NUMBER_LABEL_LENGTH];
    const char *out;

    const header_field_info *hfinfo = fi->hfinfo;

    /* Figure out the bit width */
    if (fi->flags & FI_VARINT)
        bitwidth = fi->length*8;
    else
        bitwidth = hfinfo_container_bitwidth(hfinfo);

    /* Un-shift bits */
    if (is_signed)
        value = fvalue_get_sinteger(fi->value);
    else
        value = fvalue_get_uinteger(fi->value);

    unshifted_value = value;
    if (hfinfo->bitmask) {
        unshifted_value <<= hfinfo_bitshift(hfinfo);
    }

    /* Create the bitfield first */
    if (fi->flags & FI_VARINT)
        p = decode_bitfield_varint_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
    else
        p = decode_bitfield_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
    bitfield_byte_length = (int) (p - label_str);

    /* Fill in the textual info using stored (shifted) value */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_t fmtfunc = (const custom_fmt_func_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc);
        fmtfunc(tmp, value);
        label_fill(label_str, bitfield_byte_length, hfinfo, tmp);
    }
    else if (hfinfo->strings) {
        const char *val_str = hf_try_val_to_str(value, hfinfo);

        out = hfinfo_number_vals_format(hfinfo, buf, value);
        if (hfinfo->display & BASE_SPECIAL_VALS) {
            /*
             * Unique values only display value_string string
             * if there is a match. Otherwise it's just a number
             */
            if (val_str) {
                label_fill_descr(label_str, bitfield_byte_length, hfinfo, val_str, out);
            } else {
                label_fill(label_str, bitfield_byte_length, hfinfo, out);
            }
        } else {
            if (val_str == NULL)
                val_str = "Unknown";

            if (out == NULL) /* BASE_NONE so don't put integer in descr */
                label_fill(label_str, bitfield_byte_length, hfinfo, val_str);
            else
                label_fill_descr(label_str, bitfield_byte_length, hfinfo, val_str, out);
        }
    }
    else {
        out = hfinfo_number_value_format(hfinfo, buf, value);

        label_fill(label_str, bitfield_byte_length, hfinfo, out);
    }
}

static void fill_label_bitfield64(const field_info *fi, char *label_str, bool is_signed)
{
    char       *p;
    int         bitfield_byte_length, bitwidth;
    uint64_t    value, unshifted_value;
    char        buf[NUMBER_LABEL_LENGTH];
    const char *out;

    const header_field_info *hfinfo = fi->hfinfo;

    /* Figure out the bit width */
    if (fi->flags & FI_VARINT)
        bitwidth = fi->length*8;
    else
        bitwidth = hfinfo_container_bitwidth(hfinfo);

    /* Un-shift bits */
    if (is_signed)
        value = fvalue_get_sinteger64(fi->value);
    else
        value = fvalue_get_uinteger64(fi->value);

    unshifted_value = value;
    if (hfinfo->bitmask) {
        unshifted_value <<= hfinfo_bitshift(hfinfo);
    }

    /* Create the bitfield first */
    if (fi->flags & FI_VARINT)
        p = decode_bitfield_varint_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
    else
        p = decode_bitfield_value(label_str, unshifted_value, hfinfo->bitmask, bitwidth);
    bitfield_byte_length = (int) (p - label_str);

    /* Fill in the textual info using stored (shifted) value */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_64_t fmtfunc64 = (const custom_fmt_func_64_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc64);
        fmtfunc64(tmp, value);
        label_fill(label_str, bitfield_byte_length, hfinfo, tmp);
    }
    else if (hfinfo->strings) {
        const char *val_str = hf_try_val64_to_str(value, hfinfo);

        out = hfinfo_number_vals_format64(hfinfo, buf, value);
        if (hfinfo->display & BASE_SPECIAL_VALS) {
            /*
             * Unique values only display value_string string
             * if there is a match. Otherwise it's just a number
             */
            if (val_str) {
                label_fill_descr(label_str, bitfield_byte_length, hfinfo, val_str, out);
            } else {
                label_fill(label_str, bitfield_byte_length, hfinfo, out);
            }
        } else {
            if (val_str == NULL)
                val_str = "Unknown";

            if (out == NULL) /* BASE_NONE so don't put integer in descr */
                label_fill(label_str, bitfield_byte_length, hfinfo, val_str);
            else
                label_fill_descr(label_str, bitfield_byte_length, hfinfo, val_str, out);
        }
    }
    else {
        out = hfinfo_number_value_format64(hfinfo, buf, value);

        label_fill(label_str, bitfield_byte_length, hfinfo, out);
    }
}

static void fill_label_char(const field_info *fi, char *label_str)
{
    const header_field_info *hfinfo = fi->hfinfo;
    uint32_t           value;

    char               buf[32];
    const char        *out;

    value = fvalue_get_uinteger(fi->value);

    /* Fill in the textual info */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_t fmtfunc = (const custom_fmt_func_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc);
        fmtfunc(tmp, value);
        label_fill(label_str, 0, hfinfo, tmp);
    }
    else if (hfinfo->strings) {
        const char *val_str = hf_try_val_to_str_const(value, hfinfo, "Unknown");

        out = hfinfo_char_vals_format(hfinfo, buf, value);
        label_fill_descr(label_str, 0, hfinfo, val_str, out);
    }
    else {
        out = hfinfo_char_value_format(hfinfo, buf, value);

        label_fill(label_str, 0, hfinfo, out);
    }
}

static void fill_label_number(const field_info *fi, char *label_str, bool is_signed)
{
    const header_field_info *hfinfo = fi->hfinfo;
    uint32_t           value;

    char               buf[NUMBER_LABEL_LENGTH];
    const char        *out;

    if (is_signed)
        value = fvalue_get_sinteger(fi->value);
    else
        value = fvalue_get_uinteger(fi->value);

    /* Fill in the textual info */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_t fmtfunc = (const custom_fmt_func_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc);
        fmtfunc(tmp, value);
        label_fill(label_str, 0, hfinfo, tmp);
    }
    else if (hfinfo->strings && hfinfo->type != FT_FRAMENUM) {
        /*
         * It makes no sense to have a value-string table for a
         * frame-number field - they're just integers giving
         * the ordinal frame number.
         */
        const char *val_str = hf_try_val_to_str(value, hfinfo);

        out = hfinfo_number_vals_format(hfinfo, buf, value);
        if (hfinfo->display & BASE_SPECIAL_VALS) {
            /*
             * Unique values only display value_string string
             * if there is a match. Otherwise it's just a number
             */
            if (val_str) {
                label_fill_descr(label_str, 0, hfinfo, val_str, out);
            } else {
                label_fill(label_str, 0, hfinfo, out);
            }
        } else {
            if (val_str == NULL)
                val_str = "Unknown";

            if (out == NULL) /* BASE_NONE so don't put integer in descr */
                label_fill(label_str, 0, hfinfo, val_str);
            else
                label_fill_descr(label_str, 0, hfinfo, val_str, out);
        }
    }
//    else if (IS_BASE_PORT(hfinfo->display)) {
//        char tmp[ITEM_LABEL_LENGTH];

//        port_with_resolution_to_str_buf(tmp, sizeof(tmp),
//            display_to_port_type((field_display_e)hfinfo->display), value);
//        label_fill(label_str, 0, hfinfo, tmp);
//    }
    else {
        out = hfinfo_number_value_format(hfinfo, buf, value);

        label_fill(label_str, 0, hfinfo, out);
    }
}

static void fill_label_number64(const field_info *fi, char *label_str, bool is_signed)
{
    const header_field_info *hfinfo = fi->hfinfo;
    uint64_t           value;

    char               buf[NUMBER_LABEL_LENGTH];
    const char        *out;

    if (is_signed)
        value = fvalue_get_sinteger64(fi->value);
    else
        value = fvalue_get_uinteger64(fi->value);

    /* Fill in the textual info */
    if (hfinfo->display == BASE_CUSTOM) {
        char tmp[ITEM_LABEL_LENGTH];
        const custom_fmt_func_64_t fmtfunc64 = (const custom_fmt_func_64_t)hfinfo->strings;

        DISSECTOR_ASSERT(fmtfunc64);
        fmtfunc64(tmp, value);
        label_fill(label_str, 0, hfinfo, tmp);
    }
    else if (hfinfo->strings) {
        const char *val_str = hf_try_val64_to_str(value, hfinfo);

        out = hfinfo_number_vals_format64(hfinfo, buf, value);
        if (hfinfo->display & BASE_SPECIAL_VALS) {
            /*
             * Unique values only display value_string string
             * if there is a match. Otherwise it's just a number
             */
            if (val_str) {
                label_fill_descr(label_str, 0, hfinfo, val_str, out);
            } else {
                label_fill(label_str, 0, hfinfo, out);
            }
        } else {
            if (val_str == NULL)
                val_str = "Unknown";

            if (out == NULL) /* BASE_NONE so don't put integer in descr */
                label_fill(label_str, 0, hfinfo, val_str);
            else
                label_fill_descr(label_str, 0, hfinfo, val_str, out);
        }
    }
    else {
        out = hfinfo_number_value_format64(hfinfo, buf, value);

        label_fill(label_str, 0, hfinfo, out);
    }
}

static size_t fill_display_label_float(const field_info *fi, char *label_str)
{
    int display;
    int digits;
    int n;
    double value;

    display = FIELD_DISPLAY(fi->hfinfo->display);
    value = fvalue_get_floating(fi->value);

    if (display == BASE_CUSTOM) {
        const custom_fmt_func_double_t fmtfunc = (const custom_fmt_func_double_t)fi->hfinfo->strings;
        DISSECTOR_ASSERT(fmtfunc);
        fmtfunc(label_str, value);
        return strlen(label_str);
    }

    switch (display) {
        case BASE_NONE:
            if (fi->hfinfo->type == FT_FLOAT)
                digits = FLT_DIG;
            else
                digits = DBL_DIG;

            n = snprintf(label_str, ITEM_LABEL_LENGTH, "%.*g", digits, value);
            break;
        case BASE_DEC:
            n = snprintf(label_str, ITEM_LABEL_LENGTH, "%f", value);
            break;
        case BASE_HEX:
#ifdef __MINGW32__
        double_to_hexfp(label_str, ITEM_LABEL_LENGTH, value);
#else
            n = snprintf(label_str, ITEM_LABEL_LENGTH, "%a", value);
#endif
            break;
        case BASE_EXP:
            n = snprintf(label_str, ITEM_LABEL_LENGTH, "%e", value);
            break;
        default:
        break;
    }
    if (n < 0) {
        return 0; /* error */
    }
    if ((fi->hfinfo->strings) && (fi->hfinfo->display & BASE_UNIT_STRING)) {
        const char *hf_str_val;
        hf_str_val = hf_try_double_val_to_str(value, fi->hfinfo);
        n += protol_strlcpy(label_str + n, hf_str_val, ITEM_LABEL_LENGTH - n);
    }
    if (n > ITEM_LABEL_LENGTH) {
        printf("label length too small");
        return strlen(label_str);
    }

    return n;
}

void fill_label_float(const field_info *fi, char *label_str)
{
    char tmp[ITEM_LABEL_LENGTH];

    fill_display_label_float(fi, tmp);
    label_fill(label_str, 0, fi->hfinfo, tmp);
}

int hfinfo_bitshift(const header_field_info *hfinfo)
{
    return ws_ctz(hfinfo->bitmask);
}

static int hfinfo_bitoffset(const header_field_info *hfinfo)
{
    if (!hfinfo->bitmask) {
        return 0;
    }

    /* ilog2 = first set bit, counting 0 as the last bit; we want 0
     * as the first bit */
    return hfinfo_container_bitwidth(hfinfo) - 1 - ws_ilog2(hfinfo->bitmask);
}

static int hfinfo_mask_bitwidth(const header_field_info *hfinfo)
{
    if (!hfinfo->bitmask) {
        return 0;
    }

    /* ilog2 = first set bit, ctz = last set bit */
    return ws_ilog2(hfinfo->bitmask) - ws_ctz(hfinfo->bitmask) + 1;
}

static int hfinfo_type_bitwidth(enum ftenum type)
{
    int bitwidth = 0;

    switch (type) {
    case FT_CHAR:
    case FT_UINT8:
    case FT_INT8:
        bitwidth = 8;
        break;
    case FT_UINT16:
    case FT_INT16:
        bitwidth = 16;
        break;
    case FT_UINT24:
    case FT_INT24:
        bitwidth = 24;
        break;
    case FT_UINT32:
    case FT_INT32:
        bitwidth = 32;
        break;
    case FT_UINT40:
    case FT_INT40:
        bitwidth = 40;
        break;
    case FT_UINT48:
    case FT_INT48:
        bitwidth = 48;
        break;
    case FT_UINT56:
    case FT_INT56:
        bitwidth = 56;
        break;
    case FT_UINT64:
    case FT_INT64:
        bitwidth = 64;
        break;
    default:
        DISSECTOR_ASSERT_NOT_REACHED();
        break;
    }
    return bitwidth;
}

static int hfinfo_container_bitwidth(const header_field_info *hfinfo)
{
    if (!hfinfo->bitmask) {
        return 0;
    }

    if (hfinfo->type == FT_BOOLEAN) {
        return hfinfo->display; /* hacky? :) */
    }

    return hfinfo_type_bitwidth(hfinfo->type);
}

static int hfinfo_hex_digits(const header_field_info *hfinfo)
{
    int bitwidth;

    /* If we have a bitmask, hfinfo->type is the width of the container, so not
     * appropriate to determine the number of hex digits for the field.
     * So instead, we compute it from the bitmask.
     */
    if (hfinfo->bitmask != 0) {
        bitwidth = hfinfo_mask_bitwidth(hfinfo);
    } else {
        bitwidth = hfinfo_type_bitwidth(hfinfo->type);
    }

    /* Divide by 4, rounding up, to get number of hex digits. */
    return (bitwidth + 3) / 4;
}

const char *hfinfo_char_value_format_display(int display, char buf[7], uint32_t value)
{
    char *ptr = &buf[6];
    static const char hex_digits[16] =
    { '0', '1', '2', '3', '4', '5', '6', '7',
      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    *ptr = '\0';
    *(--ptr) = '\'';
    /* Properly format value */
    if (g_ascii_isprint(value)) {
        /*
         * Printable, so just show the character, and, if it needs
         * to be escaped, escape it.
         */
        *(--ptr) = value;
        if (value == '\\' || value == '\'')
            *(--ptr) = '\\';
    } else {
        /*
         * Non-printable; show it as an escape sequence.
         */
        switch (value) {

        case '\0':
            /*
             * Show a NUL with only one digit.
             */
            *(--ptr) = '0';
            break;

        case '\a':
            *(--ptr) = 'a';
            break;

        case '\b':
            *(--ptr) = 'b';
            break;

        case '\f':
            *(--ptr) = 'f';
            break;

        case '\n':
            *(--ptr) = 'n';
            break;

        case '\r':
            *(--ptr) = 'r';
            break;

        case '\t':
            *(--ptr) = 't';
            break;

        case '\v':
            *(--ptr) = 'v';
            break;

        default:
            switch (FIELD_DISPLAY(display)) {

            case BASE_OCT:
                *(--ptr) = (value & 0x7) + '0';
                value >>= 3;
                *(--ptr) = (value & 0x7) + '0';
                value >>= 3;
                *(--ptr) = (value & 0x7) + '0';
                break;

            case BASE_HEX:
                *(--ptr) = hex_digits[value & 0x0F];
                value >>= 4;
                *(--ptr) = hex_digits[value & 0x0F];
                *(--ptr) = 'x';
                break;

            default:
                REPORT_DISSECTOR_BUG("Invalid base: %d", FIELD_DISPLAY(display));
            }
        }
        *(--ptr) = '\\';
    }
    *(--ptr) = '\'';
    return ptr;
}

static const char *hfinfo_number_value_format_display(const header_field_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint32_t value)
{
    char *ptr = &buf[NUMBER_LABEL_LENGTH-1];
    bool isint = FT_IS_INT(hfinfo->type);

    *ptr = '\0';
    /* Properly format value */
    switch (FIELD_DISPLAY(display)) {
    case BASE_DEC:
        return isint ? int_to_str_back(ptr, (int32_t) value) : uint_to_str_back(ptr, value);

    case BASE_DEC_HEX:
        *(--ptr) = ')';
        ptr = hex_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));
        *(--ptr) = '(';
        *(--ptr) = ' ';
        ptr = isint ? int_to_str_back(ptr, (int32_t) value) : uint_to_str_back(ptr, value);
        return ptr;

    case BASE_OCT:
        return oct_to_str_back(ptr, value);

    case BASE_HEX:
        return hex_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));

    case BASE_HEX_DEC:
        *(--ptr) = ')';
        ptr = isint ? int_to_str_back(ptr, (int32_t) value) : uint_to_str_back(ptr, value);
        *(--ptr) = '(';
        *(--ptr) = ' ';
        ptr = hex_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));
        return ptr;

//        case BASE_PT_UDP:
//        case BASE_PT_TCP:
//        case BASE_PT_DCCP:
//        case BASE_PT_SCTP:
//            port_with_resolution_to_str_buf(buf, NUMBER_LABEL_LENGTH,
//                    display_to_port_type((field_display_e)display), value);
//            return buf;
//        case BASE_OUI:
//            {
//            uint8_t p_oui[3];
//            const char *manuf_name;

//            p_oui[0] = value >> 16 & 0xFF;
//            p_oui[1] = value >> 8 & 0xFF;
//            p_oui[2] = value & 0xFF;

//            /* Attempt an OUI lookup. */
//            manuf_name = uint_get_manuf_name_if_known(value);
//            if (manuf_name == NULL) {
//                /* Could not find an OUI. */
//                snprintf(buf, NUMBER_LABEL_LENGTH, "%02x:%02x:%02x", p_oui[0], p_oui[1], p_oui[2]);
//            }
//            else {
//                /* Found an address string. */
//                snprintf(buf, NUMBER_LABEL_LENGTH, "%02x:%02x:%02x (%s)", p_oui[0], p_oui[1], p_oui[2], manuf_name);
//            }
//            return buf;
//            }

    default:
        REPORT_DISSECTOR_BUG("Invalid base: %d", FIELD_DISPLAY(display));
        break;
    }
    return ptr;
}

static const char *hfinfo_number_value_format_display64(const header_field_info *hfinfo, int display, char buf[NUMBER_LABEL_LENGTH], uint64_t value)
{
    char *ptr = &buf[NUMBER_LABEL_LENGTH-1];
    bool isint = FT_IS_INT(hfinfo->type);

    *ptr = '\0';
    /* Properly format value */
    switch (FIELD_DISPLAY(display)) {
    case BASE_DEC:
        return isint ? int64_to_str_back(ptr, (int64_t) value) : uint64_to_str_back(ptr, value);

    case BASE_DEC_HEX:
        *(--ptr) = ')';
        ptr = hex64_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));
        *(--ptr) = '(';
        *(--ptr) = ' ';
        ptr = isint ? int64_to_str_back(ptr, (int64_t) value) : uint64_to_str_back(ptr, value);
        return ptr;

    case BASE_OCT:
        return oct64_to_str_back(ptr, value);

    case BASE_HEX:
        return hex64_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));

    case BASE_HEX_DEC:
        *(--ptr) = ')';
        ptr = isint ? int64_to_str_back(ptr, (int64_t) value) : uint64_to_str_back(ptr, value);
        *(--ptr) = '(';
        *(--ptr) = ' ';
        ptr = hex64_to_str_back_len(ptr, value, hfinfo_hex_digits(hfinfo));
        return ptr;

    default:
        REPORT_DISSECTOR_BUG("Invalid base: %d", FIELD_DISPLAY(display));
        break;
    }

    return ptr;
}

static const char *hfinfo_number_value_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value)
{
    int display = hfinfo->display;

    if (hfinfo->type == FT_FRAMENUM) {
        /*
         * Frame numbers are always displayed in decimal.
         */
        display = BASE_DEC;
    }

    return hfinfo_number_value_format_display(hfinfo, display, buf, value);
}

static const char *hfinfo_number_value_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value)
{
    int display = hfinfo->display;

    if (hfinfo->type == FT_FRAMENUM) {
        /*
         * Frame numbers are always displayed in decimal.
         */
        display = BASE_DEC;
    }

    return hfinfo_number_value_format_display64(hfinfo, display, buf, value);
}

static const char *hfinfo_char_value_format(const header_field_info *hfinfo, char buf[32], uint32_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    return hfinfo_char_value_format_display(display, buf, value);
}

static const char *hfinfo_numeric_value_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    if (hfinfo->type == FT_FRAMENUM) {
        /*
         * Frame numbers are always displayed in decimal.
         */
        display = BASE_DEC;
    }

//    if (IS_BASE_PORT(display)) {
//        display = BASE_DEC;
//    } else if (display == BASE_OUI) {
//        display = BASE_HEX;
//    }

    switch (display) {
    case BASE_NONE:
    /* case BASE_DEC: */
    case BASE_DEC_HEX:
    case BASE_OCT: /* XXX, why we're changing BASE_OCT to BASE_DEC? */
    case BASE_CUSTOM:
        display = BASE_DEC;
        break;

    /* case BASE_HEX: */
    case BASE_HEX_DEC:
        display = BASE_HEX;
        break;
    }

    return hfinfo_number_value_format_display(hfinfo, display, buf, value);
}

static const char *hfinfo_numeric_value_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    if (hfinfo->type == FT_FRAMENUM) {
        /*
         * Frame numbers are always displayed in decimal.
         */
        display = BASE_DEC;
    }

    switch (display) {
    case BASE_NONE:
    /* case BASE_DEC: */
    case BASE_DEC_HEX:
    case BASE_OCT: /* XXX, why we're changing BASE_OCT to BASE_DEC? */
    case BASE_CUSTOM:
        display = BASE_DEC;
        break;

    /* case BASE_HEX: */
    case BASE_HEX_DEC:
        display = BASE_HEX;
        break;
    }

    return hfinfo_number_value_format_display64(hfinfo, display, buf, value);
}

static const char *hfinfo_char_vals_format(const header_field_info *hfinfo, char buf[32], uint32_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    return hfinfo_char_value_format_display(display, buf, value);
}

static const char *hfinfo_number_vals_format(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint32_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    if (display == BASE_NONE)
        return NULL;

    if (display == BASE_DEC_HEX)
        display = BASE_DEC;
    if (display == BASE_HEX_DEC)
        display = BASE_HEX;

    return hfinfo_number_value_format_display(hfinfo, display, buf, value);
}

static const char *hfinfo_number_vals_format64(const header_field_info *hfinfo, char buf[NUMBER_LABEL_LENGTH], uint64_t value)
{
    /* Get the underlying BASE_ value */
    int display = FIELD_DISPLAY(hfinfo->display);

    if (display == BASE_NONE)
        return NULL;

    if (display == BASE_DEC_HEX)
        display = BASE_DEC;
    if (display == BASE_HEX_DEC)
        display = BASE_HEX;

    return hfinfo_number_value_format_display64(hfinfo, display, buf, value);
}

const char *proto_registrar_get_name(const int n)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->name;
}

const char *proto_registrar_get_abbrev(const int n)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->abbrev;
}

enum ftenum proto_registrar_get_ftype(const int n)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->type;
}

int proto_registrar_get_parent(const int n)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return hfinfo->parent;
}

bool proto_registrar_is_protocol(const int n)
{
    header_field_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(n, hfinfo);
    return (((hfinfo->id != hf_text_only) && (hfinfo->parent == -1)) ? true : false);
}

typedef struct {
    unsigned        offset;
    field_info *finfo;
    tvbuff_t   *tvb;
} offset_search_t;

static bool check_for_offset(proto_node *node, void * data)
{
    field_info    *fi        = PNODE_FINFO(node);
    offset_search_t    *offsearch = (offset_search_t *)data;

    /* !fi == the top most container node which holds nothing */
    if (fi && !proto_item_is_hidden(node) && !proto_item_is_generated(node) && fi->ds_tvb && offsearch->tvb == fi->ds_tvb) {
        if (offsearch->offset >= (unsigned) fi->start &&
                offsearch->offset < (unsigned) (fi->start + fi->length)) {

            offsearch->finfo = fi;
            return false; /* keep traversing */
        }
    }
    return false; /* keep traversing */
}

/* Search a proto_tree backwards (from leaves to root) looking for the field
 * whose start/length occupies 'offset' */
/* XXX - I couldn't find an easy way to search backwards, so I search
 * forwards, w/o stopping. Therefore, the last finfo I find will the be
 * the one I want to return to the user. This algorithm is inefficient
 * and could be re-done, but I'd have to handle all the children and
 * siblings of each node myself. When I have more time I'll do that.
 * (yeah right) */
field_info *proto_find_field_from_offset(proto_tree *tree, unsigned offset, tvbuff_t *tvb)
{
    offset_search_t    offsearch;

    offsearch.offset = offset;
    offsearch.finfo  = NULL;
    offsearch.tvb    = tvb;

    proto_tree_traverse_pre_order(tree, check_for_offset, &offsearch);

    return offsearch.finfo;
}

bool tree_expanded(int tree_type)
{
    if (tree_type <= 0) {
        return false;
    }
//    ws_assert(tree_type >= 0 && tree_type < num_tree_types);
    return tree_is_expanded[tree_type >> 5] & (1U << (tree_type & 31));
}

void tree_expanded_set(int tree_type, bool value)
{
//    ws_assert(tree_type >= 0 && tree_type < num_tree_types);

    if (value)
        tree_is_expanded[tree_type >> 5] |= (1U << (tree_type & 31));
    else
        tree_is_expanded[tree_type >> 5] &= ~(1U << (tree_type & 31));
}


unsigned char proto_check_field_name(const char *field_name)
{
    return module_check_valid_name(field_name, false);
}

unsigned char proto_check_field_name_lower(const char *field_name)
{
    return module_check_valid_name(field_name, true);
}
